Json.Net property name changed after serialization - c#

Lets say I have some class that I have previously serialized with Json.Net
public class BillingAddress
{
public string BillingCity { get; set; }
public string BillingState { get; set; }
public string BillingStreet { get; set; }
public string BillingZip { get; set; }
}
However, I had to go back and change BillingStreet to BillingStreet1, and then again later on to BillingStreetA. I am trying to find a way in Json.Net to support either an attribute or custom converter that can deserialize a property that has been known as a different name previously, and also supports having more than one different previous name (such as in my example).
I have a custom converter that relies on an attribute as follows:
[JsonVersionedPropertyName("BillingStreetA", "BillingStreet1")]
public string BillingStreet { get; set; }
public class VersionedPropertyNameConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
//return objectType.IsClass;
return (objectType == typeof(IPaymentMethod));
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
PropertyInfo[] props = objectType.GetProperties();
var versionedProps = props.Where(x => Attribute.IsDefined(x, typeof(JsonVersionedPropertyName)));
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
foreach (var versProp in versionedProps)
{
var attrs = versProp.GetCustomAttributes(true);
foreach (var att in attrs)
{
var versionedProp = att as JsonVersionedPropertyName;
if (versionedProp != null)
{
var versions = versionedProp.VersionedNames;
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && (string.Equals(pi.Name, jp.Name, StringComparison.OrdinalIgnoreCase) || versions.Contains(jp.Name)));
if (prop != null)
prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
}
}
}
return instance;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
However, when I try to use this converter with another converter, my my entire deserialization fails. I am trying to figure out if theres a better way to do this (versioned property names) instead of what I have above.

You're close. Something like this works. I'll leave it as an excercise to genericize it :):
public class OninaigConverter : JsonConverter
{
private readonly Dictionary<string, string> _propertyMappings = new Dictionary<string, string>
{
{"BillingStreetA", "BillingStreet"},
{"BillingStreet1", "BillingStreet"}
};
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType.GetTypeInfo().IsClass;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = Activator.CreateInstance(objectType);
var props = objectType.GetTypeInfo().DeclaredProperties.ToList();
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
if (!_propertyMappings.TryGetValue(jp.Name, out var name))
name = jp.Name;
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
}

Related

Json.Net: How to ignore null elements in array deserializing a JSON

I have this JSON:
{
"Variable1": "1",
"Variable2": "50000",
"ArrayObject": [null]
}
I have this stubs:
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<ArrayObject> ArrayObject { get; set; }
}
public class ArrayObject
{
public string VariableArray1 { get; set; }
public string VariableArray2 { get; set; }
}
I'd like to ignore the null elements inside array preferably using the json settings or some sort of converter. So the result should be an empty array in that case or null.
Here is the code I've been trying to make this work.
class Program
{
static void Main(string[] args)
{
string json = #"{
""Variable1"": ""1"",
""Variable2"": ""50000"",
""ArrayObject"": [null]
}";
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
};
Class1 class1 = JsonConvert.DeserializeObject<Class1>(json, settings);
Console.WriteLine(class1.ArrayObject == null);
Console.WriteLine(class1.ArrayObject.Count());
foreach (var item in class1.ArrayObject)
{
Console.WriteLine(item.VariableArray1);
Console.WriteLine(item.VariableArray2);
Console.WriteLine("#######################");
}
}
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<ArrayObject> ArrayObject { get; set; }
}
public class ArrayObject
{
public string VariableArray1 { get; set; }
public string VariableArray2 { get; set; }
}
}
I thought that using NullValueHandling = NullValueHandling.Ignore would make it work. Apparently not. Any ideas?
Update: I need a global solution, I don't want to have to modify every viewmodel inside my project.
Setting NullValueHandling = NullValueHandling.Ignore will not filter null values from JSON arrays automatically during deserialization because doing so would cause the remaining items in the array to be re-indexed, rendering invalid any array indices that might have been stored elsewhere in the serialization graph.
If you don't care about preserving array indices and want to filter null values from the array during deserialization anyway, you will need to implement a custom JsonConverter such as the following:
public class NullFilteringListConverter<T> : JsonConverter<List<T>>
{
public override List<T> ReadJson(JsonReader reader, Type objectType, List<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
var list = existingValue as List<T> ?? (List<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, list);
list.RemoveAll(i => i == null);
return list;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) => throw new NotImplementedException();
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
And apply it to your model as follows:
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
[JsonConverter(typeof(NullFilteringListConverter<ArrayObject>))]
public List<ArrayObject> ArrayObject { get; set; }
}
Or, add it in settings as follows:
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Converters = { new NullFilteringListConverter<ArrayObject>() },
};
Notes:
Since you didn't ask about filtering null values during serialization, I didn't implement it, however it would be easy to do by changing CanWrite => true; and replacing WriteJson() with:
public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) => serializer.Serialize(writer, value.Where(i => i != null));
Demo fiddles here and here.
Update
I need a global solution. If you need to automatically filter all null values from all possible List<T> objects in every model, the following JsonConverter will do the job:
public class NullFilteringListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType.IsArray || objectType == typeof(string) || objectType.IsPrimitive)
return false;
var itemType = objectType.GetListItemType();
return itemType != null && (!itemType.IsValueType || Nullable.GetUnderlyingType(itemType) != null);
}
object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var list = existingValue as List<T> ?? (List<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, list);
list.RemoveAll(i => i == null);
return list;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
var itemType = objectType.GetListItemType();
var method = typeof(NullFilteringListConverter).GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
try
{
return method.MakeGenericMethod(new[] { itemType }).Invoke(this, new object[] { reader, objectType, existingValue, serializer });
}
catch (Exception ex)
{
// Wrap the TargetInvocationException in a JsonSerializerException
throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
}
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
public static partial class JsonExtensions
{
internal static Type GetListItemType(this Type type)
{
// Quick reject for performance
if (type.IsPrimitive || type.IsArray || type == typeof(string))
return null;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}
And add it to settings as follows:
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Converters = { new NullFilteringListConverter() },
};
Class1 class1 = JsonConvert.DeserializeObject<Class1>(json, settings);
With this converter, adding [JsonConverter(typeof(NullFilteringListConverter<ArrayObject>))] to ArrayObject is no longer required. Do note that all List<T> instances in your deserialization graph may get re-indexed whenever these settings are used! Make sure you really want this as the side-effects of changing indices of items referred to elsewhere by index may include data corruption (incorrect references) rather than an outright ArgumentOutOfRangeException.
Demo fiddle #3 here.
You could also have a custom setter that filters out null values.
private List<ArrayObject> _arrayObject;
public List<ArrayObject> ArrayObject
{
get => _arrayObject;
set
{
_arrayObject = value.Where(x => x != null).ToList();
}
}
Fiddle working here https://dotnetfiddle.net/ePp0A2
NullValueHandling.Ignore does not filter null values from an array. But you can do this easily by adding a deserialization callback method in your class to filter out the nulls:
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
public List<ArrayObject> ArrayObject { get; set; }
[OnDeserialized]
internal void OnDeserialized(StreamingContext context)
{
ArrayObject?.RemoveAll(o => o == null);
}
}
Working demo: https://dotnetfiddle.net/v9yn7j

Serializing object to JSON - Get attribute Name from a field of serializing class

I have a ParameterInfo class.
[JsonObject(MemberSerialization.OptIn)]
public class ParameterInfo
{
public Parameter Param { get; protected set; }
[JsonProperty(Order = 0)]
public string Name { get => GetCultureName(); }
[JsonProperty(Order = 1)]
public string Value { get => Param.AsString(); }
}
I need to serialize my class to the json in such a way:
{"PHY_MATERIAL_PARAM_EXP_COEFF2": 0.0}
I can only get this instead
{
"Name": "PHY_MATERIAL_PARAM_EXP_COEFF2",
"Value": 0.0
}
how can I do it using Newton Json with? I am looking for a way with highest performance.
You can try to write your custom JsonConverter:
public class ParameterInfoJsonConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(ParameterInfo);
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer) => throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null) return;
writer.WriteStartObject();
var val = (ParameterInfo)value;
writer.WritePropertyName(val.Name);
writer.WriteValue(val.Value);
writer.WriteEndObject();
}
}
And either mark class with [JsonConverter(typeof(ParameterInfoJsonConverter))] attribute or pass it to JsonConvert:
JsonConvert.SerializeObject(new ParameterInfo(), new ParameterInfoJsonConverter())

Custom JsonConverter not called for nested property

I have a class Employee where Manager property itself is of type Employee
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Employee Manager { get; set; }
public IList<string> Roles { get; set; }
}
I want to create a custom JsonConverter for Employee type.
public class TestJsonConverter : JsonConverter
{
public TestJsonConverter()
{
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
IList<string> propertyNames = o.Properties().Select(p => p.Name).ToList();
o.AddFirst(new JProperty("Keys", new JArray(propertyNames)));
o.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Employee));
}
public override bool CanRead
{
get { return false; }
}
}
The ContractResolver is
class ContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
if (objectType == typeof(Employee))
{
contract.Converter = new TestJsonConverter();
}
return contract;
}
}
When I try to serialize the Employee object, custom JsonConverter is getting called only for the top level Employee object, not for the nested Manager property which is also of Employee type:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new ContractResolver();
string json = JsonConvert.SerializeObject(employee, Formatting.Indented, settings);
I think you run into the same issue described in
Custom JsonConverter WriteJson Does Not Alter Serialization of Sub-properties:
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.
The solution provided should be adaptable to your case.
So the JsonConverter runs once, starting at the root node.
You will need to navigate this JSon Object tree and update it yourself.
Not sure it this is what you intended to achieve but I've tried it myself and you can decide if this works for you or not.
public class TestJsonConverter : JsonConverter
{
public TestJsonConverter()
{
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject root = (JObject)t;
var stack = new Stack<JObject>();
stack.Push(root);
while (stack.Any())
{
var current = stack.Pop();
var propertyNames = current.Properties().Select(p => p.Name).ToArray();
current.AddFirst(new JProperty("Keys", new JArray(propertyNames)));
var nestedObjects = current.Properties().Where(p => p.Value.Type == JTokenType.Object).ToArray();
foreach (var nestedObj in nestedObjects)
{
stack.Push((JObject)nestedObj.Value);
}
}
root.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Employee));
}
public override bool CanRead
{
get { return false; }
}
}

How can I flatten a Dictionary member into the containing object when serializing to Json? [duplicate]

I'm using Json.Net for serialization.
I have a class with a Dictionary:
public class Test
{
public string X { get; set; }
public Dictionary<string, string> Y { get; set; }
}
Can I somehow serialize this object to get the following JSON
{
"X" : "value",
"key1": "value1",
"key2": "value2"
}
where "key1", "key2" are keys in the Dictionary?
If you're using Json.Net 5.0.5 or later and you're willing to change the type of your dictionary from Dictionary<string, string> to Dictionary<string, object>, then one easy way to accomplish what you want is to add the [JsonExtensionData] attribute to your dictionary property like this:
public class Test
{
public string X { get; set; }
[JsonExtensionData]
public Dictionary<string, object> Y { get; set; }
}
The keys and values of the marked dictionary will then be serialized as part of the parent object. The bonus is that it works on deserialization as well: any properties in the JSON that do not match to members of the class will be placed into the dictionary.
Implement JsonConverter-derived class: the CustomCreationConverter class should be used as base class to create a custom object.
Draft version of the converter (error handling can be improved as you wish):
internal class TestObjectConverter : CustomCreationConverter<Test>
{
#region Overrides of CustomCreationConverter<Test>
public override Test Create(Type objectType)
{
return new Test
{
Y = new Dictionary<string, string>()
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
// Write properties.
var propertyInfos = value.GetType().GetProperties();
foreach (var propertyInfo in propertyInfos)
{
// Skip the Y property.
if (propertyInfo.Name == "Y")
continue;
writer.WritePropertyName(propertyInfo.Name);
var propertyValue = propertyInfo.GetValue(value);
serializer.Serialize(writer, propertyValue);
}
// Write dictionary key-value pairs.
var test = (Test)value;
foreach (var kvp in test.Y)
{
writer.WritePropertyName(kvp.Key);
serializer.Serialize(writer, kvp.Value);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jsonObject = JObject.Load(reader);
var jsonProperties = jsonObject.Properties().ToList();
var outputObject = Create(objectType);
// Property name => property info dictionary (for fast lookup).
var propertyNames = objectType.GetProperties().ToDictionary(pi => pi.Name, pi => pi);
foreach (var jsonProperty in jsonProperties)
{
// If such property exists - use it.
PropertyInfo targetProperty;
if (propertyNames.TryGetValue(jsonProperty.Name, out targetProperty))
{
var propertyValue = jsonProperty.Value.ToObject(targetProperty.PropertyType);
targetProperty.SetValue(outputObject, propertyValue, null);
}
else
{
// Otherwise - use the dictionary.
outputObject.Y.Add(jsonProperty.Name, jsonProperty.Value.ToObject<string>());
}
}
return outputObject;
}
public override bool CanWrite
{
get { return true; }
}
#endregion
}
Client code:
var test = new Test
{
X = "123",
Y = new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" },
{ "key3", "value3" },
}
};
string json = JsonConvert.SerializeObject(test, Formatting.Indented, new TestObjectConverter());
var deserializedObject = JsonConvert.DeserializeObject<Test>(json);
Please note: there is a potential collision between property names and key names of the dictionary.
You can create this converter and then assign it to your property. Took bits and pieces of of proposed solutions.
public class DictionaryToJsonObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDictionary<string, string>).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(JsonConvert.SerializeObject(value, Formatting.Indented));
}
}
Then use it in your poco class.
public class Poco
{
[JsonProperty("myid")]
public string Id{ get; set; }
[JsonProperty("properties")]
[JsonConverter(typeof(DictionaryToJsonObjectConverter))]
public IDictionary<string, string> Properties { get; set; }
}

How do you modify the Json serialization of just one field using Json.net?

Say for example I'm trying to convert an object with 10 fields to Json, however I need to modify the process of serializing 1 of these fields. At the moment, I'd have to use manually write out each property like this:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("Field1");
serializer.Serialize(writer, value.Field1);
writer.WritePropertyName("Field2");
serializer.Serialize(writer, value.Field2);
writer.WritePropertyName("Field3");
serializer.Serialize(writer, value.Field3);
writer.WritePropertyName("Field4");
serializer.Serialize(writer, Convert.ToInt32(value.Field4)); //Modifying one field here
//Six more times
writer.WriteEndObject();
}
This isn't good code, and it's really irritating to have to write. Is there any way of getting Json.net to serialize all but one property automatically? Or possibly generate a JObject automatically and modify that?
You can try by decorating the property you need to modify manually with JsonConverterAttribute and pass the appropriate JsonConverter type.
For example, using OP's original example:
public class IntegerConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, Convert.ToInt32(value));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
}
class TestJson
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
[JsonConverter(typeof(IntegerConverter))]
public string Field4 { get; set; }
}
You can then serialize the object as usual using JsonConvert:
var test = new TestJson {Field1 = "1", Field2 = "2", Field3 = "3", Field4 = "4"};
var jsonString = JsonConvert.SerializeObject(test);
If you have access to the class (and you always need it to be serialized the same way) you could modify the class to your needs. Suppose this.is the class:
public class MyClass
{
public string Value4 {get; set;}
}
If you want value 4 to be serialized as an int you could do this:
public class MyClass
{
[JsonIgnore()]
public string Value4 {get; set;}
public int Value4AsInt
{
return Convert.ToInt32(Value4);
}
}
You might use System.Reflection, however it's slow but you don't have to modify the class
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
Type vType = value.GetType();
MemberInfo[] properties = vType.GetProperties(BindingFlags.Public
| BindingFlags.Instance);
foreach (PropertyInfo property in properties)
{
object serValue = null;
if (property.Name == "Field4")
{
serValue = Convert.ToInt32(property.GetValue(value, null));
}
else
{
serValue = property.GetValue(value, null);
}
writer.WritePropertyName(property.Name);
serializer.Serialize(writer, serValue);
}
writer.WriteEndObject();
}

Categories

Resources