Custom JsonConverter not called for nested property - c#

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; }
}
}

Related

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())

Json.Net property name changed after serialization

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;
}
}

Is this proper usage of IReferenceResolver inside a JsonConverter?

I'm using JSON.NET to deserialize JSON (which I don't control) that represents an Event of a specific type with inner data:
{
"id": "abc",
"type": "a",
"data": {
// Data specific to "type"="a".
"a": 1
}
}
and
{
"id": "def",
"type": "b",
"data": {
// Data specific to "type"="b".
"b": 1
}
}
This JSON should be deserialized to the following classes:
public class Event
{
public string Id { get; }
public string Type { get; }
public EventDataBase Data { get; }
public Event(string id, string type, EventDataBase data)
{
this.Id = id;
this.Type = type;
this.Data = data;
}
}
public abstract class EventDataBase
{
}
public class AEventData : EventDataBase
{
public string A { get; }
public AEventData(string a)
{
this.A = a;
}
}
public class BEventData : EventDataBase
{
public string B { get; }
public BEventData(string b)
{
this.B = b;
}
}
The correct inherited class of EventDataBase should be instantiated when deserializing the Event.
There are many solutions to this problem, but they usually involve having the type belong on the data, not the parent. And if it does belong on the parent, the answer is to usually convert the JSON for Event as a JObject and then manually deserialize it. While this is possible, it feels like a hack to leverage only part of JSON.NET to do the deserialization.
The solution I've come up uses IReferenceResolver to add a reference to the type, and then when attempting to deserialize the data, get the reference to the parent type, and use it to determine the concrete class that needs to be deserialized:
public class CustomContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member.DeclaringType == typeof(Event) && member.Name == nameof(Event.Type))
{
property.Order = Int32.MinValue;
property.MemberConverter = new EventTypeConverter();
}
else if (member.DeclaringType == typeof(Event) && member.Name == nameof(Event.Data))
{
property.Order = Int32.MaxValue;
property.MemberConverter = new EventDataConverter();
}
return property;
}
private class EventTypeConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var eventType = reader.Value as string;
serializer.ReferenceResolver.AddReference(serializer, "parentEvent.type", eventType);
return eventType;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
private class EventDataConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(EventDataBase);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var eventType = serializer.ReferenceResolver.ResolveReference(serializer, "parentEvent.type") as string;
var eventDataType = GetEventDataTypeFromEventType(eventType);
return serializer.Deserialize(reader, eventDataType);
}
private static Type GetEventDataTypeFromEventType(string eventType)
{
switch (eventType?.ToLower())
{
case "a":
return typeof(AEventData);
case "b":
return typeof(BEventData);
default:
throw new InvalidOperationException();
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
Here's a .NET Fiddle sample of this in action.
Is this valid use of the IReferenceResolver? Does JSON.NET clear the reference resolver during each call deserialize (so that multiple calls don't store the old reference)? Is it thread-safe?

Deserialize JSON array of arrays

I have the following JSON data which I'd like to deserialize to a C# POCO object but I'm having trouble deserializing the array of arrays.
var json = #"{
""name"": ""Foo"",
""pages"": [
{
""page"": 1,
""fields"": [
{
""name"": ""stuffs"",
""rows"": [
[{ ""value"" : ""$199""}, { ""value"": ""foo"" }],
[{ ""value"" : ""$222""}, { ""value"": ""bar"", ""color"": ""blue"" }]
]
}]
}
]
}";
The exception is
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'UserQuery+TableRow' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'rows[0]', line 4, position 5.
Following the advise of the exception message, I did attempt all of those things but only to be faced with more errors.
These are my POCO objects
public class Document
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("pages")]
public Page[] Pages { get; set; }
}
public class Page
{
[JsonProperty("page")]
public int PageNumber { get; set; }
[JsonProperty("fields")]
public FieldBase[] FieldsBase { get; set; }
}
public class TableRow
{
public Cell[] Cells { get; set; }
}
public class Cell
{
[JsonProperty("value")]
public string Value { get; set; }
[JsonProperty("color")]
public string Color { get; set; }
}
public abstract class FieldBase
{
[JsonProperty("name")]
public string Name { get; set; }
}
public class Table : FieldBase
{
[JsonProperty("rows")]
public TableRow[] Rows { get; set; } = new TableRow[0];
}
And my field converter to deal with the abstract class (not sure if this matters)
public class FieldConverter : JsonConverter
{
static JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new BaseSpecifiedConcreteClassConverter() };
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(FieldBase));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return JsonConvert.DeserializeObject<Table>(jo.ToString(), SpecifiedSubclassConversion);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // won't be called because CanWrite returns false
}
}
public class BaseSpecifiedConcreteClassConverter : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (typeof(FieldBase).IsAssignableFrom(objectType) && !objectType.IsAbstract)
return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow)
return base.ResolveContractConverter(objectType);
}
}
And the following line of code which, when executed in LINQPad, produces the error
JsonConvert.DeserializeObject<Document>(json, new FieldConverter()).Dump();
Any help would be greatly appreciated.
In your json, "rows" is a jagged array:
"rows": [[{ "value" : "$199"}, { "value": "foo" }]]
However, in your object model this corresponds to an array of TableRow classes that contain an array of cells. Thus you will need another JsonConverter to serialize each TableRow as an array of cells and not an object:
public class TableRowConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TableRow);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var cells = serializer.Deserialize<Cell[]>(reader);
return new TableRow { Cells = cells };
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var row = (TableRow)value;
serializer.Serialize(writer, row.Cells);
}
}
public class JsonDerivedTypeConverter<TBase, TDerived> : JsonConverter where TDerived : TBase
{
public JsonDerivedTypeConverter()
{
if (typeof(TBase) == typeof(TDerived))
throw new InvalidOperationException("TBase and TDerived cannot be identical");
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TBase);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<TDerived>(reader);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, to deserialize, do:
var settings = new JsonSerializerSettings
{
Converters = new JsonConverter[] { new TableRowConverter(), new JsonDerivedTypeConverter<FieldBase, Table>() },
};
var doc = JsonConvert.DeserializeObject<Document>(json, settings);
Example fiddle.

Want to Serialization Json include [JsonIgnore] in sometimes

I have classes that 95% don't want to serialization some negation properties. So I use [JsonIgnore] attributes & Newtonsoft.Json, and it works fine.
However, I have only few methods want to return JSON that includes properties from [JsonIgnore]. How I can do that?
Thank you all
public class SubCatalog
{
[Key]
public int Id { get; set; }
[ForeignKey("Catalog")]
public int CatalogId { get; set; }
public string Name { get; set; }
[JsonIgnore]
public virtual Catalog Catalog { get; set; }
[JsonIgnore]
public virtual IList<Item> Items { get; set; }
}
With this method, I want to include all properties in entity.
public HttpResponseMessage GetSubCatalogs(int id)
{
var list = _uow.SubCatalogs.GetByCatalogId(id);
var json = JsonConvert.SerializeObject(list,
Formatting.Indented,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
return new HttpResponseMessage()
{
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
};
}
You may use the IContractResolver
public class IncludeIgnored : DefaultContractResolver
{
private readonly bool _includeIgnored;
public IncludeIgnored(bool includeIgnored)
{
_includeIgnored = includeIgnored;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
foreach (var item in properties)
{
item.Ignored = !_includeIgnored;
}
return properties;
}
}
Then when you call your serializer
var serializedObject= JsonConvert.SerializeObject(myObject, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new IncludeIgnored(true) });
You can try to implement custom JsonConverter.
override WriteJson method and implement custom serialization there.
public class Converter : JsonConverter
{
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return true; } }
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SubCatalog);
}
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)
{
//serialization code here
}
}
Add instance of this converter to Converters property of serializer or JsonSettings

Categories

Resources