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 theRoles
collection - The second
SelectToken
retrieved theRole
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 JObject
s. 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
C# Json.Net Convention That Follows Ruby Property Naming Conventions
How to Do a Deep Copy of an Object in .Net
The Entity Cannot Be Constructed in a Linq to Entities Query
How to Get the Path of the Assembly the Code Is In
Await' Works, But Calling Task.Result Hangs/Deadlocks
What Are the Correct Version Numbers For C#
Why Is There a Default Instance of Every Form in Vb.Net But Not in C#
Is There a Better Alternative Than This to 'Switch on Type'
Parse Email Content from Quoted Reply
How to Parse a Json String That Would Cause Illegal C# Identifiers
An Async/Await Example That Causes a Deadlock
Type Checking: Typeof, Gettype, or Is
Validation Failed For One or More Entities. See 'Entityvalidationerrors' Property For More Details