How to Implement Custom Jsonconverter in Json.Net

How to implement custom JsonConverter in JSON.NET?

Using the standard CustomCreationConverter, I was struggling to work how to generate the correct type (Person or Employee), because in order to determine this you need to analyse the JSON and there is no built in way to do this using the Create method.

I found a discussion thread pertaining to type conversion and it turned out to provide the answer. Here is a link: Type converting (archived link).

What's required is to subclass JsonConverter, overriding the ReadJson method and creating a new abstract Create method which accepts a JObject.

The JObject class provides a means to load a JSON object and
provides access to the data within this object.

The overridden ReadJson method creates a JObject and invokes the Create method (implemented by our derived converter class), passing in the JObject instance.

This JObject instance can then be analysed to determine the correct type by checking existence of certain fields.

Example

string json = "[{
\"Department\": \"Department1\",
\"JobTitle\": \"JobTitle1\",
\"FirstName\": \"FirstName1\",
\"LastName\": \"LastName1\"
},{
\"Department\": \"Department2\",
\"JobTitle\": \"JobTitle2\",
\"FirstName\": \"FirstName2\",
\"LastName\": \"LastName2\"
},
{\"Skill\": \"Painter\",
\"FirstName\": \"FirstName3\",
\"LastName\": \"LastName3\"
}]";

List<Person> persons =
JsonConvert.DeserializeObject<List<Person>>(json, new PersonConverter());

...

public class PersonConverter : JsonCreationConverter<Person>
{
protected override Person Create(Type objectType, JObject jObject)
{
if (FieldExists("Skill", jObject))
{
return new Artist();
}
else if (FieldExists("Department", jObject))
{
return new Employee();
}
else
{
return new Person();
}
}

private bool FieldExists(string fieldName, JObject jObject)
{
return jObject[fieldName] != null;
}
}

public abstract class JsonCreationConverter<T> : JsonConverter
{
/// <summary>
/// Create an instance of objectType, based properties in the JSON object
/// </summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">
/// contents of JSON object that will be deserialized
/// </param>
/// <returns></returns>
protected abstract T Create(Type objectType, JObject jObject);

public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}

public override bool CanWrite
{
get { return false; }
}

public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);

// Create target object based on JObject
T target = Create(objectType, jObject);

// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);

return target;
}
}

Json.NET Custom JsonConverter with data types

Although this JSON format is somewhat unusual and resists the use of attributes due to the dynamic property names, it is still possible to make a JsonConverter to deserialize it into your preferred class structure with one small change: I would recommend you change the Children property in the Root class to be an ICollection<object> to mirror the Children property in the Type1 class. As it is now, it does not match the structure of your desired output (where Children is shown as an array, not an object) and would otherwise require additional code in the converter to handle properly.

class Root
{
public string Author { get; set; }
public string Version { get; set; }
public ICollection<object> Children { get; set; }
}

Here is what I came up with for the converter (assuming the above change is made):

class CustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Root));
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
Root root = new Root();
root.Author = (string)obj["Author"];
root.Version = (string)obj["Version"];
root.Children = ((Type1)DeserializeTypeX(obj, serializer)).Children;
return root;
}

private object DeserializeTypeX(JObject obj, JsonSerializer serializer)
{
JProperty prop = obj.Properties().Where(p => p.Name.StartsWith("data.")).First();
JObject child = (JObject)prop.Value;
if (prop.Name == "data.Type1")
{
List<object> children = new List<object>();
foreach (JObject jo in child["Children"].Children<JObject>())
{
children.Add(DeserializeTypeX(jo, serializer));
}
return new Type1 { Children = children };
}
else if (prop.Name == "data.Type2")
{
return child.ToObject<Type2>(serializer);
}
else if (prop.Name == "data.Type3")
{
return child.ToObject<Type3>(serializer);
}
throw new JsonSerializationException("Unrecognized type: " + prop.Name);
}

public override bool CanWrite
{
get { return false; }
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Armed with this converter you can deserialize to your classes like this:

Root root = JsonConvert.DeserializeObject<Root>(json, new CustomConverter());

You can then serialize to the new format like this:

JsonSerializerSettings settings = new JsonSerializerSettings
{
DateFormatString = "yyyy-MM-dd",
Formatting = Formatting.Indented
};

Console.WriteLine(JsonConvert.SerializeObject(root, settings));

Fiddle: https://dotnetfiddle.net/ESNMLE

Custom JSON Deserialization in C# with JsonConverter

It seems like you only want to perform custom serialization of TimeSpan as it belongs to Ownership, so why not make a converter for TimeSpan only and save yourself from manually serializing all of the other class properties?:

public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeSpan.FromTicks(reader.GetInt64());
}

public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Ticks);
}
}

Then decorate your MeanInterval property with a JsonConverterAttribute:

public class Ownership
{
public string OwnershipId { get; set; }
public List<string> TextOutput { get; set; }
public DateTime DateTime { get; set; }
[JsonConverter(typeof(TimeSpanConverter))]
public TimeSpan MeanInterval { get; set; }// Like long ticks, TimeSpan.FromTicks(Int64), TimeSpan.Ticks
}

Try it online

Custom JSONConverter in NewtonsoftJson for Serialization

I leave another post because the OP's question has changed a lot since its original version.

Just to make sure that our understanding are the same:

  • You want to use JsonConvert.SerializeXNode to convert a xml to a json
  • This method by default does not handle arrays gracefully
    • Related documentation
    • Related github issue
  • You also want to change the representation of Role node to have a wrapper object around them

Let's tackle problems one by one

XML node to Json array

In order to treat the LegalEntities and Roles as array you need to add a custom attribute to these xml nodes: json:Array = "true".

This json namespace has to be defined inside the xml like this: xmlns:json="http://james.newtonking.com/projects/json"

So, you need to modify your xml to this (either manually or through System.Xml / System.Xml.Linq):

<Message xmlns:json="http://james.newtonking.com/projects/json">
<MessageInfo>
<Guid>be190914-4b18-4454-96ec-67887dd4d7a7</Guid>
<SourceId>101</SourceId>
</MessageInfo>
<LegalEntities json:Array="true">
<LegalEntity>
<Roles json:Array="true">
<Role>
<LEAssociateTypeId>101</LEAssociateTypeId>
<LEAssociateTypeId_Value>Client/Counterparty</LEAssociateTypeId_Value>
<LastUpdatedDate>2021-08-07T23:05:17</LastUpdatedDate>
<LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
<LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
</Role>
<Role>
<LEAssociateTypeId>6000</LEAssociateTypeId>
<LEAssociateTypeId_Value>Account Owner</LEAssociateTypeId_Value>
<LastUpdatedDate>2021-08-07T21:20:07</LastUpdatedDate>
<LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
<LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
</Role>
<Role>
<LEAssociateTypeId>5003</LEAssociateTypeId>
<LEAssociateTypeId_Value>Investment Manager</LEAssociateTypeId_Value>
<LastUpdatedDate>2021-08-16T06:12:59</LastUpdatedDate>
<LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
<LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
</Role>
</Roles>
</LegalEntity>
</LegalEntities>
</Message>

Now if you pass this to the SerializeXNode then you will get the following json:

{
"Message":{
"MessageInfo":{
"Guid":"be190914-4b18-4454-96ec-67887dd4d7a7",
"SourceId":"101"
},
"LegalEntities":[
{
"LegalEntity":{
"Roles":[
{
"Role":[
{
"LEAssociateTypeId":"101",
"LEAssociateTypeId_Value":"Client/Counterparty",
"LastUpdatedDate":"2021-08-07T23:05:17",
"LegalEntityRoleStatusId":"3",
"LegalEntityRoleStatusId_Value":"Active"
},
{
"LEAssociateTypeId":"6000",
"LEAssociateTypeId_Value":"Account Owner",
"LastUpdatedDate":"2021-08-07T21:20:07",
"LegalEntityRoleStatusId":"3",
"LegalEntityRoleStatusId_Value":"Active"
},
{
"LEAssociateTypeId":"5003",
"LEAssociateTypeId_Value":"Investment Manager",
"LastUpdatedDate":"2021-08-16T06:12:59",
"LegalEntityRoleStatusId":"3",
"LegalEntityRoleStatusId_Value":"Active"
}
]
}
]
}
}
]
}
}

Transform Role nodes

In order to be able to perform some transformation you need to retrieve the appropriate node via the SelectToken method

var doc = XDocument.Parse(xml);
var json = JsonConvert.SerializeXNode(doc);

var root = JObject.Parse(json);
var roles = root.SelectToken("$.Message.LegalEntities[0].LegalEntity.Roles") as JArray;
if (roles == null) return;

var role = roles.First().SelectToken("$.Role") as JArray;
if (roles == null) return;
  • The first SelectToken retrieved the Roles collection
  • The second SelectToken retrieved the Role collection

Now, let's do the transformation:

var roleNodes = new List<JObject>();
foreach (var roleNode in role)
{
roleNodes.Add(new JObject(new JProperty("Role", roleNode)));
}

Here we iterate through the Role collection and we are creating a wrapper object around it which has a single property called Role.

Finally we have to replace the Roles collection with the newly created JObjects. This can be achieved with the following simple command:

roles.ReplaceAll(roleNodes);

For the sake of completeness here is the full code:

var xml = File.ReadAllText("sample.xml");

var doc = XDocument.Parse(xml);
var json = JsonConvert.SerializeXNode(doc);

var root = JObject.Parse(json);
var roles = root.SelectToken("$.Message.LegalEntities[0].LegalEntity.Roles") as JArray;
if (roles == null) return;

var role = roles.First().SelectToken("$.Role") as JArray;
if (roles == null) return;

var roleNodes = new List<JObject>();
foreach (var roleNode in role)
{
roleNodes.Add(new JObject(new JProperty("Role", roleNode)));
}

roles.ReplaceAll(roleNodes);
Console.WriteLine(root);

and the emitted output:

{
"Message": {
"MessageInfo": {
"Guid": "be190914-4b18-4454-96ec-67887dd4d7a7",
"SourceId": "101"
},
"LegalEntities": [
{
"LegalEntity": {
"Roles": [
{
"Role": {
"LEAssociateTypeId": "101",
"LEAssociateTypeId_Value": "Client/Counterparty",
"LastUpdatedDate": "2021-08-07T23:05:17",
"LegalEntityRoleStatusId": "3",
"LegalEntityRoleStatusId_Value": "Active"
}
},
{
"Role": {
"LEAssociateTypeId": "6000",
"LEAssociateTypeId_Value": "Account Owner",
"LastUpdatedDate": "2021-08-07T21:20:07",
"LegalEntityRoleStatusId": "3",
"LegalEntityRoleStatusId_Value": "Active"
}
},
{
"Role": {
"LEAssociateTypeId": "5003",
"LEAssociateTypeId_Value": "Investment Manager",
"LastUpdatedDate": "2021-08-16T06:12:59",
"LegalEntityRoleStatusId": "3",
"LegalEntityRoleStatusId_Value": "Active"
}
}
]
}
}
]
}
}

How to make use of a custom JsonConverter to deserialise only a child object in Json.NET?

The solution is to clear the list of converters of the JsonSerializer before calling JsonSerializer.Deserialize from my JsonConverter.

I got the idea from here, in which a user describes how nulling out the JsonConverter of a JsonContract reverts to the default JsonConverter. This avoids the problem of repeatedly calling my custom JsonConverter for the child object I wish to deserialise.

public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer) {
while (reader.Depth != 3) {
reader.Read();
}

serializer.Converters.Clear();

return serializer.Deserialize(reader, objectType);
}

The code has also been simplified, removing the try - finally block. It's not necessary to read the ending tokens because there will never be an attempt to deserialise them anyway. There is also no need to check for the depth because the custom JsonConverter will only be used once.

How to apply a custom JsonConverter to the values inside a list inside a dictionary?

Dictionary<string, List<int>> is a nested collection of collections, and you are looking for something like ItemOfItemsConverterType, corresponding to ItemConverterType, to specify a converter for the items of the items of the collection. Unfortunately, no such attribute is implemented. Instead, it will be necessary to create a converter for the nested List<int> collection that calls the required innermost item converter.

This can be done by implementing the following JsonConverter decorator for List<>:

public class ListItemConverterDecorator : JsonConverter
{
readonly JsonConverter itemConverter;

public ListItemConverterDecorator(Type type) =>
itemConverter = (JsonConverter)Activator.CreateInstance(type ?? throw new ArgumentNullException());

public override bool CanConvert(Type objectType) =>
!objectType.IsPrimitive && objectType != typeof(string) && objectType.BaseTypesAndSelf().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>));

public override bool CanRead => itemConverter.CanRead;
public override bool CanWrite => itemConverter.CanWrite;

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var itemType = objectType.BaseTypesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>)).Select(t => t.GetGenericArguments()[0]).First();
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartArray)
throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, JsonToken.StartArray));
var list = existingValue as IList ?? (IList)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray)
list.Add(itemConverter.ReadJson(reader, itemType, null, serializer));
return list;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartArray();
foreach (var item in (IList)value)
if (item == null)
writer.WriteNull();
else
itemConverter.WriteJson(writer, item, serializer);
writer.WriteEndArray();
}
}

public static partial class JsonExtensions
{
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();

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

public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}

Then annotate your Example class as follows, using JsonPropertyAttribute.ItemConverterParameters to specify the inner item converter CustomConverter:

public class Example 
{
[JsonConverter(typeof(CustomConverter))]
public int ExampleInt { get; set; }
[JsonProperty(ItemConverterType = typeof(CustomConverter))]
public List<int> ExampleList { get; set; }

[JsonProperty(ItemConverterType = typeof(ListItemConverterDecorator),
ItemConverterParameters = new object [] { typeof(CustomConverter) })]
public Dictionary<string, List<int>> ExampleDictionary { get; set; }
}

And now you should be all set. Demo fiddle here.

Implementation custom Newtonsoft JsonConverter (reader)

You could define it as following

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
// Convert the Base64 Value to Bytes
var dataBytes = Convert.FromBase64String(token.Value<String>());
// Deserialize the Binary Formatted Data
using (MemoryStream ms = new MemoryStream(dataBytes))
{
IFormatter br = new BinaryFormatter();
return Convert.ChangeType(br.Deserialize(ms),objectType);
}
}



Related Topics



Leave a reply



Submit