id field resolution when using JObject.FromObject - c#

I have a family of custom Json Converters. They work like this:
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) {
JObject jo = JObject.FromObject(value);
// do my own stuff to the JObject here -- basically adding a property. The value of the property depends on the specific converter being used.
jo.WriteTo(writer, this);
}
The problem with this is that the id field of the JObject is always 1. Not good. So I tried using an inner serializer to get the id field:
private JsonSerializer _InnerSerializer {get;set;}
private JsonSerializer InnerSerializer {
get {
if (_InnerSerializer == null) {
_InnerSerializer = new JsonSerializer();
}
return _InnerSerializer;
}
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) {
JsonSerializer inner = this.InnerSerializer;
jo = JObject.FromObject(value, inner);
//my stuff here
jo.WriteTo(writer, this);
}
That gives a different id each time, even if it hits the same object twice. What I really want is to use Json's usual id resolution with my custom serialization. How can I do that?

Your idea of using an inner serializer will not work as-is. The id-to-object mapping table is held in a private field JsonSerializerInternalBase._mappings with no way to copy it from the outer serializer to the inner serializer.
As an alternative, you could make a recursive call to serialize using the same serializer, and have the converter disable itself using a thread-static pushdown stack along the lines of Generic method of modifying JSON before being returned to client and JSON.Net throws StackOverflowException when using [JsonConvert()]. You would need to enhance the converters in these examples to manually check for and add the necessary "$id" and "$ref" properties by making use of the JsonSerializer.ReferenceResolver property.
However, since your converters just add properties, a more straightforward solution to your problem might be to create a custom contract resolver that allows types to customize their contract as it is generated via a callback method declared in an attribute applied to the type, for instance:
public class ModifierContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static ModifierContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static ModifierContractResolver() { instance = new ModifierContractResolver(); }
public static ModifierContractResolver Instance { get { return instance; } }
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
// Apply in reverse order so inherited types are applied after base types.
foreach (var attr in objectType.GetCustomAttributes<JsonObjectContractModifierAttribute>(true).Reverse())
{
var modifier = (JsonObjectContractModifier)Activator.CreateInstance(attr.ContractModifierType, true);
modifier.ModifyContract(objectType, contract);
}
return contract;
}
}
public abstract class JsonObjectContractModifier
{
public abstract void ModifyContract(Type objectType, JsonObjectContract contract);
}
[System.AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class JsonObjectContractModifierAttribute : System.Attribute
{
private readonly Type _contractModifierType;
public Type ContractModifierType { get { return _contractModifierType; } }
public JsonObjectContractModifierAttribute(Type contractModifierType)
{
if (contractModifierType == null)
{
throw new ArgumentNullException("contractModifierType");
}
if (!typeof(JsonObjectContractModifier).IsAssignableFrom(contractModifierType))
{
throw new ArgumentNullException(string.Format("{0} is not a subtype of {1}", contractModifierType, typeof(JsonObjectContractModifier)));
}
this._contractModifierType = contractModifierType;
}
}
Then, apply it to your types as in the following example:
[JsonObjectContractModifier(typeof(TestContractModifier))]
public class Test
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
class TestContractModifier : JsonObjectContractModifier
{
class EmptyValueProvider : IValueProvider
{
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static EmptyValueProvider() { }
internal static readonly EmptyValueProvider Instance = new EmptyValueProvider();
#region IValueProvider Members
public object GetValue(object target)
{
var test = target as Test;
if (test == null)
return null;
return test.A == null && test.B == null && test.C == null;
}
public void SetValue(object target, object value)
{
var property = target as Test;
if (property == null)
return;
if (value != null && value.GetType() == typeof(bool) && (bool)value == true)
{
property.A = property.B = property.C = null;
}
}
#endregion
}
public override void ModifyContract(Type objectType, JsonObjectContract contract)
{
var jsonProperty = new JsonProperty
{
PropertyName = "isEmpty",
UnderlyingName = "isEmpty",
PropertyType = typeof(bool?),
NullValueHandling = NullValueHandling.Ignore,
Readable = true,
Writable = true,
DeclaringType = typeof(Test),
ValueProvider = EmptyValueProvider.Instance,
};
contract.Properties.Add(jsonProperty);
}
}
And serialize as follows:
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects, // Or PreserveReferencesHandling.All
ContractResolver = ModifierContractResolver.Instance,
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
This produces the following JSON:
[
{
"$id": "1",
"A": "hello",
"B": "goodbye",
"C": "sea",
"isEmpty": false
},
{
"$ref": "1"
},
{
"$id": "2",
"A": null,
"B": null,
"C": null,
"isEmpty": true
},
}
As you can see, both the synthetic "isEmpty" property and reference handling properties are present. Prototype fiddle.

Related

Deserializing json - mapping single property with no direct json match

I am trying to deserialize an existing JSON structure to into an object composed of a set of models. The naming in these models are not consistent and I was specifically asked to not change them (renaming, adding attributes, etc).
So, given this Json text (just a small sample):
{
"parameter": {
"alarms": [
{
"id": 1,
"name": "alarm1",
"type": 5,
"min": 0,
"max": 2
}],
"setting-active": true,
"setting-oneRun": true
}
}
would need to be mapped into these models:
public class Alarm
{
public int AlarmId { get; set; }
public string AlarmName { get; set; }
public AlarmType RbcType { get; set; }
public int MinimumTolerated { get; set; }
public int MaximumTolerated { get; set; }
}
public class Setting
{
public bool Active { get; set; }
public bool OneRun { get; set; }
}
public class Parameter
{
public List<Alarm> Alarms { get; set; }
public Setting ParameterSetting { get; set; }
}
So far, im writing a class that extends DefaultContractResolver and overrides maps property names.
MyCustomResolver so far:
public class MyCustomResolver : DefaultContractResolver
{
private Dictionary<string, string>? _propertyMappings;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
//ModelMappings is a static class that will return a dictionary with mappings per ObjType being deserialized
_propertyMappings = ModelMappings.GetMapping(type);
return base.CreateProperties(type, memberSerialization);
}
protected override string ResolvePropertyName(string propertyName)
{
if (_propertyMappings != null)
{
_propertyMappings.TryGetValue(propertyName, out string? resolvedName);
return resolvedName ?? base.ResolvePropertyName(propertyName);
}
return base.ResolvePropertyName(propertyName);
}
}
Code that Im using to deserialize:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new MyCustomResolver();
Parameter p = JsonConvert.DeserializeObject<Parameter>(jsonString, settings);
So I reached a point I need to somehow map the properties in Parameter to values located in the prev json node ("setting-active", "setting-oneRun"). I need to tell the deserializer where these values are.
Can this be done using an extension of DefaultContractResolver ?
I appreciate any tips pointing in the right direction
You can apply ModelMappings.GetMapping(objectType) in DefaultContractResolver.CreateObjectContract():
public class MyCustomResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
var overrides = ModelMappings.GetMapping(objectType);
if (overrides != null)
{
foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.UnderlyingName != null && overrides.TryGetValue(property.UnderlyingName, out var name))
property.PropertyName = name;
}
}
return contract;
}
}
Notes:
By applying the mappings in CreateObjectContract() you can remap both property names and creator parameter names.
Since the contract resolver is designed to resolve contracts for all types, storing a single private Dictionary<string, string>? _propertyMappings; doesn't really make sense.
Unlike your previous question, your current question shows properties from a nested c# object ParameterSetting getting percolated up to the parent object Parameter. Since a custom contract resolver is designed to generate the contract for a single type, it isn't suited to restructuring data between types. Instead, consider using a DTO or converter + DTO in such situations:
public class ParameterConverter : JsonConverter<Parameter>
{
record ParameterDTO(List<Alarm> alarms, [property: JsonProperty("setting-active")] bool? Active, [property: JsonProperty("setting-oneRun")] bool? OneRun);
public override void WriteJson(JsonWriter writer, Parameter? value, JsonSerializer serializer)
{
var dto = new ParameterDTO(value!.Alarms, value.ParameterSetting?.Active, value.ParameterSetting?.OneRun);
serializer.Serialize(writer, dto);
}
public override Parameter? ReadJson(JsonReader reader, Type objectType, Parameter? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<ParameterDTO>(reader);
if (dto == null)
return null;
existingValue ??= new ();
existingValue.Alarms = dto.alarms;
if (dto.Active != null || dto.OneRun != null)
existingValue.ParameterSetting = new () { Active = dto.Active.GetValueOrDefault(), OneRun = dto.OneRun.GetValueOrDefault() };
return existingValue;
}
}
If your "real" model is too complex to define a DTO, you could create a JsonConverter<Paramater> that (de)serializes the JSON into an intermediate JToken hierarchy, then restructures that. See e.g. this answer to Can I serialize nested properties to my class in one operation with Json.net?.
In some cases, the custom naming of your properties is just camel casing. To camel case property names without the need for explicit overrides, set MyCustomResolver.NamingStrategy to CamelCaseNamingStrategy e.g. as follows:
var settings = new JsonSerializerSettings
{
DateFormatString = "YYYY-MM-DD",
// Use CamelCaseNamingStrategy since many properties in the JSON are just camel-cased.
ContractResolver = new MyCustomResolver { NamingStrategy = new CamelCaseNamingStrategy() },
Converters = { new ParameterConverter() },
};
Demo fiddle here.
I think that the best way to "KEEP IT SIMPLE", you need to define an object that has exactly the properties of the json. Then you can use a library like "Automapper" to define rules of mapping between the "json object" and the "business object".

Custom JsonConvertor that also serializes it's value minus one of it's properties

I am having trouble with the requirement to serialize an object in a specific way whereby the object id value becomes the key and the rest of the object forms the value.
Simplified class to be serialized:
[JsonConverter(typeof(FieldTypeConvertor))]
public class FieldType {
public string Id { get; set; }
public string Condition { get; set; }
public string FieldType { get; set; }
public string Label { get; set; }
public string Options { get; set; }
}
Here is my JsonConvertor WriteJson method:
public override void WriteJson(JsonWriter writer, UmbracoFormFieldDto value, JsonSerializer serializer)
{
var props = value.GetType().GetProperties();
var idProp = props.FirstOrDefault(p => p.Name.Equals("id", StringComparison.OrdinalIgnoreCase));
var key = idProp.GetValue(value, null).ToString();
var newObj = JsonConvert.SerializeObject(value, new JsonSerializerSettings()
{ ContractResolver = new IgnorePropertiesResolver(new[] { "id" }) });
var container = new JObject { { key, newObj } };
container.WriteTo(writer);
}
I get why I end up with a StackOverflow but do not know how to avoid it in order generate the output I need which is the following:
"idValueFromOriginalObj": {
"condition": "propValue",
"fieldype": "propValue",
"label": "propValue",
"options": "propValue"
}
Essentially, the value of id in the original object becomes the key in the serialized object and the rest of the original object properties form the value.
Your problem is that, inside JsonConverter.ReadJson(), you are attempting to recursively serialize your value object, but since the converter is applied directly to the type using [JsonConverter(typeof(TConverter))], you are getting a stack overflow.
There are several options to disable a converter for recursive serialization; JSON.Net throws StackOverflowException when using [JsonConvert()] details some of them. However, since you are already using a custom contract resolver IgnorePropertiesResolver to ignore properties named "id", you might enhance the resolver to also ignore converters of type FieldTypeConvertor. The following should do the trick:
public class IgnorePropertiesResolver : DefaultContractResolver
{
readonly HashSet<string> propertiesToIgnore;
readonly HashSet<Type> converterTypesToIgnore;
public IgnorePropertiesResolver(IEnumerable<string> propertiesToIgnore, IEnumerable<Type> converterTypesToIgnore) : base() =>
(this.propertiesToIgnore, this.converterTypesToIgnore) =
((propertiesToIgnore ?? throw new ArgumentNullException()).ToHashSet(StringComparer.OrdinalIgnoreCase),
(converterTypesToIgnore ?? throw new ArgumentNullException()).ToHashSet());
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (propertiesToIgnore.Contains(member.Name))
property.Ignored = true;
if (property.Converter != null && converterTypesToIgnore.Contains(property.Converter.GetType()))
property.Converter = null;
return property;
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
if (contract.Converter != null && converterTypesToIgnore.Contains(contract.Converter.GetType()))
contract.Converter = null;
return contract;
}
};
Then modify FieldTypeConvertor as follows:
public sealed class FieldTypeConvertor : JsonConverter<UmbracoFormFieldDto>
{
static readonly IContractResolver innerResolver = new IgnorePropertiesResolver(new [] { "id" }, new [] { typeof(FieldTypeConvertor) })
{
NamingStrategy = new CamelCaseNamingStrategy(),
};
public override void WriteJson(JsonWriter writer, UmbracoFormFieldDto value, JsonSerializer serializer)
{
var props = value.GetType().GetProperties();
var idProp = props.FirstOrDefault(p => p.Name.Equals("id", StringComparison.OrdinalIgnoreCase));
var key = idProp.GetValue(value, null).ToString();
writer.WriteStartObject();
writer.WritePropertyName(key);
JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = innerResolver }).Serialize(writer, value);
writer.WriteEndObject();
}
public override UmbracoFormFieldDto ReadJson(JsonReader reader, Type objectType, UmbracoFormFieldDto existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
}
And your model will be serialized as required:
{
"idValueFromOriginalObj": {
"condition": "propValue",
"fieldType": "propValue",
"label": "propValue",
"options": "propValue"
}
}
Notes:
Newtonsoft recommends you cache the contract resolver for best performance.
You should inherit from DefaultContractResolver instead of CamelCasePropertyNamesContractResolver for reasons explained in Json.Net: Html Helper Method not regenerating.
For performance reasons, I eliminated the intermediate serialization to JObject and instead serialized directly to the incoming JsonWriter.
Demo fiddle here.

How to add metadata to describe which properties are dates in JSON.Net

I would like to add a metadata property to my json so that the client side can know what properties are dates.
For example if I had an object like this:
{
"notADate": "a value",
"aDate": "2017-04-23T18:25:43.511Z",
"anotherDate": "2017-04-23T18:25:43.511Z"
}
I would like to add a metadata property to tell the consumer which properties to treat as dates something like this:
{
"_date_properties_": ["aDate", "anotherDate"],
"notADate": "a value",
"aDate": "2017-04-23T18:25:43.511Z",
"anotherDate": "2017-04-23T18:25:43.511Z"
}
Any help would be great, thanks!
You could create a custom ContractResolver that inserts a synthetic "_date_properties_" property into the contract of every object that is serialized.
To do this, first subclass DefaultContractResolver to allow contracts to be fluently customized after they have been created by application-added event handlers:
public class ConfigurableContractResolver : DefaultContractResolver
{
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;
void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}
public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}
public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }
public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}
public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}
Next, create an extension method to add the desired property to a JsonObjectContract:
public static class JsonContractExtensions
{
const string DatePropertiesName = "_date_properties_";
public static void AddDateProperties(this JsonContract contract)
{
var objectContract = contract as JsonObjectContract;
if (objectContract == null)
return;
var properties = objectContract.Properties.Where(p => p.PropertyType == typeof(DateTime) || p.PropertyType == typeof(DateTime?)).ToList();
if (properties.Count > 0)
{
var property = new JsonProperty
{
DeclaringType = contract.UnderlyingType,
PropertyName = DatePropertiesName,
UnderlyingName = DatePropertiesName,
PropertyType = typeof(string[]),
ValueProvider = new FixedValueProvider(properties.Select(p => p.PropertyName).ToArray()),
AttributeProvider = new NoAttributeProvider(),
Readable = true,
Writable = false,
// Ensure // Ensure PreserveReferencesHandling and TypeNameHandling do not apply to the synthetic property.
ItemIsReference = false,
TypeNameHandling = TypeNameHandling.None,
};
objectContract.Properties.Insert(0, property);
}
}
class FixedValueProvider : IValueProvider
{
readonly object properties;
public FixedValueProvider(object value)
{
this.properties = value;
}
#region IValueProvider Members
public object GetValue(object target)
{
return properties;
}
public void SetValue(object target, object value)
{
throw new NotImplementedException("SetValue not implemented for fixed properties; set JsonProperty.Writable = false.");
}
#endregion
}
class NoAttributeProvider : IAttributeProvider
{
#region IAttributeProvider Members
public IList<Attribute> GetAttributes(Type attributeType, bool inherit) { return new Attribute[0]; }
public IList<Attribute> GetAttributes(bool inherit) { return new Attribute[0]; }
#endregion
}
}
Finally, serialize your example type as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
// Here I am using CamelCaseNamingStrategy as is shown in your JSON.
// If you don't want camel case, leave NamingStrategy null.
NamingStrategy = new CamelCaseNamingStrategy(),
}.Configure((s, e) => { e.Contract.AddDateProperties(); }),
};
var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);
This solution only handles statically typed DateTime and DateTime? properties. If you have object-valued properties that sometimes have DateTime values, or a Dictionary<string, DateTime>, or extension data containing DateTime values, you will need a more complex solution.
(As an alternative implementation, you could instead subclass DefaultContractResolver.CreateObjectContract and hardcode the required properties there using JsonContractExtensions.AddDateProperties(), however I thought it would be more interesting to create a general-purpose, fluently configurable contract resolver in case it becomes necessary to plug in different customizations later.)
You may want to cache the contract resolver for best performance.
Sample .Net fiddle.

Why Json.NET can't deserialize structs without JsonConstructor attribute? [duplicate]

I have a class that has a default constructor and also an overloaded constructor that takes in a set of parameters. These parameters match to fields on the object and are assigned on construction. At this point i need the default constructor for other purposes so i would like to keep it if i can.
My Problem: If I remove the default constructor and pass in the JSON string, the object deserializes correctly and passes in the constructor parameters without any issues. I end up getting back the object populated the way I would expect. However, as soon as I add the default constructor into the object, when i call JsonConvert.DeserializeObject<Result>(jsontext) the properties are no longer populated.
At this point I have tried adding new JsonSerializerSettings(){CheckAdditionalContent = true} to the deserialization call. That did not do anything.
Another note: the constructor parameters do match the names of the fields exactly except that the parameters are start with a lowercase letter. I wouldn't think this would matter since, like i mentioned, the deserialization works fine with no default constructor.
Here is a sample of my constructors:
public Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Json.Net prefers to use the default (parameterless) constructor on an object if there is one. If there are multiple constructors and you want Json.Net to use a non-default one, then you can add the [JsonConstructor] attribute to the constructor that you want Json.Net to call.
[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
...
}
It is important that the constructor parameter names match the corresponding property names of the JSON object (ignoring case) for this to work correctly. You do not necessarily have to have a constructor parameter for every property of the object, however. For those JSON object properties that are not covered by the constructor parameters, Json.Net will try to use the public property accessors (or properties/fields marked with [JsonProperty]) to populate the object after constructing it.
If you do not want to add attributes to your class or don't otherwise control the source code for the class you are trying to deserialize, then another alternative is to create a custom JsonConverter to instantiate and populate your object. For example:
class ResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Result));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the JSON for the Result into a JObject
JObject jo = JObject.Load(reader);
// Read the properties which will be used as constructor parameters
int? code = (int?)jo["Code"];
string format = (string)jo["Format"];
// Construct the Result object using the non-default constructor
Result result = new Result(code, format);
// (If anything else needs to be populated on the result object, do that here)
// Return the result
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, add the converter to your serializer settings, and use the settings when you deserialize:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);
A bit late and not exactly suited here, but I'm gonna add my solution here, because my question had been closed as a duplicate of this one, and because this solution is completely different.
I needed a general way to instruct Json.NET to prefer the most specific constructor for a user defined struct type, so I can omit the JsonConstructor attributes which would add a dependency to the project where each such struct is defined.
I've reverse engineered a bit and implemented a custom contract resolver where I've overridden the CreateObjectContract method to add my custom creation logic.
public class CustomContractResolver : DefaultContractResolver {
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var c = base.CreateObjectContract(objectType);
if (!IsCustomStruct(objectType)) return c;
IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
var mostSpecific = list.LastOrDefault();
if (mostSpecific != null)
{
c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
}
return c;
}
protected virtual bool IsCustomStruct(Type objectType)
{
return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
method.ThrowIfNull("method");
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
I'm using it like this.
public struct Test {
public readonly int A;
public readonly string B;
public Test(int a, string b) {
A = a;
B = b;
}
}
var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings {
ContractResolver = new CustomContractResolver()
});
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");
Based on some of the answers here, I have written a CustomConstructorResolver for use in a current project, and I thought it might help somebody else.
It supports the following resolution mechanisms, all configurable:
Select a single private constructor so you can define one private constructor without having to mark it with an attribute.
Select the most specific private constructor so you can have multiple overloads, still without having to use attributes.
Select the constructor marked with an attribute of a specific name - like the default resolver, but without a dependency on the Json.Net package because you need to reference Newtonsoft.Json.JsonConstructorAttribute.
public class CustomConstructorResolver : DefaultContractResolver
{
public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
public bool IgnoreAttributeConstructor { get; set; } = false;
public bool IgnoreSinglePrivateConstructor { get; set; } = false;
public bool IgnoreMostSpecificConstructor { get; set; } = false;
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum) return contract;
// Look for constructor with attribute first, then single private, then most specific.
var overrideConstructor =
(this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType))
?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType))
?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));
// Set override constructor if found, otherwise use default contract.
if (overrideConstructor != null)
{
SetOverrideCreator(contract, overrideConstructor);
}
return contract;
}
private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
{
contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
contract.CreatorParameters.Clear();
foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
{
contract.CreatorParameters.Add(constructorParameter);
}
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();
if (constructors.Count == 1) return constructors[0];
if (constructors.Count > 1)
throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");
return null;
}
protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
return constructors.Length == 1 ? constructors[0] : null;
}
protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderBy(e => e.GetParameters().Length);
var mostSpecific = constructors.LastOrDefault();
return mostSpecific;
}
}
Here is the complete version with XML documentation as a gist:
https://gist.github.com/bjorn-jarisch/80f77f4b6bdce3b434b0f7a1d06baa95
Feedback appreciated.
The default behaviour of Newtonsoft.Json is going to find the public constructors. If your default constructor is only used in containing class or the same assembly, you can reduce the access level to protected or internal so that Newtonsoft.Json will pick your desired public constructor.
Admittedly, this solution is rather very limited to specific cases.
internal Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Based on the answer by Zoltan, I created a variation that lets you use a specific constructor based on its signature.
Usage
return new JsonSerializerSettings
{
ContractResolver = new DynamicObjectResolver(t =>
{
if (t == typeof(QueueProperties))
return new Type[] { typeof(string) };
return null;
})
};
An here is the implementation
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Reflection;
namespace ConsoleApp76.Json
{
class DynamicObjectResolver : DefaultContractResolver
{
private readonly Func<Type, Type[]> GetConstructorSignature;
private readonly ConcurrentDictionary<Type, ConstructorInfo> TypeToConstructorLookup =
new ConcurrentDictionary<Type, ConstructorInfo>();
public DynamicObjectResolver(Func<Type, Type[]> getConstructorSignature)
{
if (getConstructorSignature is null)
throw new ArgumentNullException(nameof(getConstructorSignature));
GetConstructorSignature = getConstructorSignature;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var result = base.CreateObjectContract(objectType);
ConstructorInfo constructor = TypeToConstructorLookup.GetOrAdd(objectType, t => FindConstructorInfo(t));
if (constructor is null)
return result;
result.OverrideCreator = CreateParameterizedConstructor(constructor);
foreach (var param in CreateConstructorParameters(constructor, result.Properties))
result.CreatorParameters.Add(param);
return result;
}
private ConstructorInfo FindConstructorInfo(Type objectType)
{
Type[] constructorSignature = GetConstructorSignature(objectType);
if (constructorSignature is null)
return null;
return objectType.GetConstructor(
bindingAttr:
System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance,
binder: null,
types: new Type[] { typeof(string) },
modifiers: null);
}
private static ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
if (method is null)
throw new ArgumentNullException(nameof(method));
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
}
Solution:
public Response Get(string jsonData) {
var json = JsonConvert.DeserializeObject<modelname>(jsonData);
var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
return data;
}
Model:
public class modelname {
public long parameter{ get; set; }
public int parameter{ get; set; }
public int parameter{ get; set; }
public string parameter{ get; set; }
}

Custom JsonConverter WriteJson Does Not Alter Serialization of Sub-properties

I always had the impression that the JSON serializer actually traverses your entire object's tree, and executes the custom JsonConverter's WriteJson function on each interface-typed object that it comes across - not so.
I have the following classes and interfaces:
public interface IAnimal
{
string Name { get; set; }
string Speak();
List<IAnimal> Children { get; set; }
}
public class Cat : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Cat()
{
Children = new List<IAnimal>();
}
public Cat(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Meow";
}
}
public class Dog : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Dog()
{
Children = new List<IAnimal>();
}
public Dog(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Arf";
}
}
To avoid the $type property in the JSON, I've written a custom JsonConverter class, whose WriteJson is
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
IAnimal animal = value as IAnimal;
JObject o = (JObject)t;
if (animal != null)
{
if (animal is Dog)
{
o.AddFirst(new JProperty("type", "Dog"));
//o.Find
}
else if (animal is Cat)
{
o.AddFirst(new JProperty("type", "Cat"));
}
foreach(IAnimal childAnimal in animal.Children)
{
// ???
}
o.WriteTo(writer);
}
}
}
In this example, yes, a dog can have cats for children and vice-versa. In the converter, I want to insert the "type" property so that it saves that to the serialization. I have the following setup. (Zoo has only a name and a list of IAnimals. I didn't include it here for brevity and laziness ;))
Zoo hardcodedZoo = new Zoo()
{ Name = "My Zoo",
Animals = new List<IAnimal> { new Dog("Ruff"), new Cat("Cleo"),
new Dog("Rover"){
Children = new List<IAnimal>{ new Dog("Fido"), new Dog("Fluffy")}
} }
};
JsonSerializerSettings settings = new JsonSerializerSettings(){
ContractResolver = new CamelCasePropertyNamesContractResolver() ,
Formatting = Formatting.Indented
};
settings.Converters.Add(new AnimalsConverter());
string serializedHardCodedZoo = JsonConvert.SerializeObject(hardcodedZoo, settings);
serializedHardCodedZoo has the following output after serialization:
{
"name": "My Zoo",
"animals": [
{
"type": "Dog",
"Name": "Ruff",
"Children": []
},
{
"type": "Cat",
"Name": "Cleo",
"Children": []
},
{
"type": "Dog",
"Name": "Rover",
"Children": [
{
"Name": "Fido",
"Children": []
},
{
"Name": "Fluffy",
"Children": []
}
]
}
]
}
The type property shows up on Ruff, Cleo, and Rover, but not for Fido and Fluffy. I guess the WriteJson isn't called recursively. How do I get that type property there?
As an aside, why does it not camel-case IAnimals like I expect it to?
The reason that your converter is not getting applied to your child objects is because JToken.FromObject() uses a new instance of the serializer internally, which does not know about your converter. There is an overload that allows you to pass in the serializer, but if you do so here you will have another problem: since you are inside a converter and you are using JToken.FromObject() to try to serialize the parent object, you will get into an infinite recursive loop. (JToken.FromObject() calls the serializer, which calls your converter, which calls JToken.FromObject(), etc.)
To get around this problem, you must handle the parent object manually. You can do this without much trouble using a bit of reflection to enumerate the parent properties:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
Type type = value.GetType();
jo.Add("type", type.Name);
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.CanRead)
{
object propVal = prop.GetValue(value, null);
if (propVal != null)
{
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
jo.WriteTo(writer);
}
Fiddle: https://dotnetfiddle.net/sVWsE4
Here's an idea, instead of doing the reflection on every property, iterate through the normally serialized JObject and then changed the token of properties you're interested in.
That way you can still leverage all the ''JsonIgnore'' attributes and other attractive features built-in.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken jToken = JToken.FromObject(value);
if (jToken.Type == JTokenType.Object)
{
JObject jObject = (JObject)jToken;
...
AddRemoveSerializedProperties(jObject, val);
...
}
...
}
And then
private void AddRemoveSerializedProperties(JObject jObject, MahMan baseContract)
{
jObject.AddFirst(....);
foreach (KeyValuePair<string, JToken> propertyJToken in jObject)
{
if (propertyJToken.Value.Type != JTokenType.Object)
continue;
JToken nestedJObject = propertyJToken.Value;
PropertyInfo clrProperty = baseContract.GetType().GetProperty(propertyJToken.Key);
MahMan nestedObjectValue = clrProperty.GetValue(baseContract) as MahMan;
if(nestedObj != null)
AddRemoveSerializedProperties((JObject)nestedJObject, nestedObjectValue);
}
}
I had this issue using two custom converters for a parent and child type. A simpler method I found is that since an overload of JToken.FromObject() takes a serializer as a parameter, you can pass along the serializer you were given in WriteJson(). However you need to remove your converter from the serializer to avoid a recursive call to it (but add it back in after):
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Converters.Remove(this);
JToken jToken = JToken.FromObject(value, serializer);
serializer.Converters.Add(this);
// Perform any necessary conversions on the object returned
}
Here is a hacky solution to your problem that gets the work done and looks tidy.
public class MyJsonConverter : JsonConverter
{
public const string TypePropertyName = "type";
private bool _dormant = false;
/// <summary>
/// A hack is involved:
/// " JToken.FromObject(value, serializer); " creates amn infinite loop in normal circumstances
/// for that reason before calling it "_dormant = true;" is called.
/// the result is that this JsonConverter will reply false to exactly one "CanConvert()" call.
/// this gap will allow to generate a a basic version without any extra properties, and then add them on the call with " JToken.FromObject(value, serializer); ".
/// </summary>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
_dormant = true;
JToken t = JToken.FromObject(value, serializer);
if (t.Type == JTokenType.Object && value is IContent)
{
JObject o = (JObject)t;
o.AddFirst(new JProperty(TypePropertyName, value.GetType().Name));
o.WriteTo(writer);
}
else
{
t.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
if (_dormant)
{
_dormant = false;
return false;
}
return true;
}
}

Categories

Resources