I'm using Newtonsoft JSON to serialization and deserialization.
I have json representing fields where title property is representing a display Name:
{
"ID": {
"type": "integer",
"title": "ID Display Name"
},
"TITLE": {
"type": "string",
"title": "Title Display name"
},
"NAME": {
"type": "string",
"title": "Name"
},
"CUSTOM_123": {
"type": "string",
"title": "CUSTOM_123",
"formLabel": "CUSTOM_123 Display Name"
}
// ... etc
}
And I have class:
public class Field
{
public string Title { get; set; }
public string Type { get; set; }
}
Now everything working fine. But when field name starts with "CUSTOM_" I need to serialize "Title" from property "formLabel".
How could I implement conditional property source?
You can parse your JSON to JObject, then enumerate all properties, deserialize them one by one to Field instance and update a Title value if property name in JSON starts with CUSTOM_ string
var json = JObject.Parse(jsonString);
foreach (var property in json.Properties())
{
var field = property.Value.ToObject<Field>();
if (property.Name.StartsWith("CUSTOM_")&&property.Value is JObject propertyObject)
{
field.Title = propertyObject["formLabel"]?.Value<string>();
}
}
This approach is simpler, rather than writing a custom converter, I believe
having your custom JsonConverter will do the trick,so you will separate the business of deserialization and you can include more business as required in the Convert Method. so in first lines newton soft serializes normally our object to Field then to have the custom business for "Custom_",working fiddle https://dotnetfiddle.net/aVpB8I
Code as below :
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
namespace testapp
{
class Program
{
static void Main(string[] args)
{
string json = #"
{
""ID"": {
""type"": ""integer"",
""title"": ""ID Display Name""
},
""TITLE"": {
""type"": ""string"",
""title"": ""Title Display name""
},
""NAME"": {
""type"": ""string"",
""title"": ""Name""
},
""CUSTOM_123"": {
""type"": ""string"",
""title"": ""CUSTOM_123"",
""formLabel"": ""CUSTOM_123 Display Name""
}
}";
List<Field> result = JsonConvert.DeserializeObject<List<Field>>(json, new JobInfoConverter());
}
public class Field
{
public string title { get; set; }
public string type { get; set; }
}
public class JobInfoConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
List<Field> result = new List<Field>();
var content = JObject.Load(reader);
foreach (var prop in content.Properties())
{
var parsedValue = prop.Value.ToObject<Field>();
if (prop.Name.StartsWith("CUSTOM_"))
{
parsedValue.title = prop.Value["formLabel"].ToString();
}
result.Add(parsedValue);
}
return result;
}
catch (Exception ex)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite => true;
}
}
}
You can deserialize to object using Newtonsoft with custom JsonConverter.
Your JSON structure, is a JSON object, that contains multiple child JSON objects (fields). So when deserializing it to .NET you could create a separate class for parent that will have a list of Fields. You could also change you JSON to array instead of object and deserialize to List<Fields>.
IList<Field> fields = JsonConvert.DeserializeObject<IList<Field>>(json, new CustomJsonConverter());
public class CustomJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
IList<Field> fields = new List<Field>();
JObject obj = JObject.Load(reader);
foreach (JToken child in obj.Children())
{
Field field = new Field();
JProperty fieldProp = (JProperty)child;
JObject fieldValue = (JObject)fieldProp.Value;
string fieldName = fieldProp.Name;
if (fieldName .StartsWith("CUSTOM_"))
{
field.Title = (string)fieldValue["formLabel"];
field.Type = (string)fieldValue["type"];
}
else
{
field.Title = (string)fieldValue["title"];
field.Type = (string)fieldValue["type"];
}
fields.Add(field);
}
return fields;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
public class Field
{
public string Title { get; set; }
public string Type{ get; set; }
}
https://dotnetfiddle.net/5bNihC
Related
In my project i receive a JSON which represents a Configuration for an device. Each device has one or more Interfaces that it can be connected with. In the property "SupportedInterfaces" i receive a List with all Interfaces that are supported for this device. The List expects the type BaseInterface but i'll receive the derived classes "RestInterface", "ModbusTcpInterface" or more to come.
I'm trying to deserialize List<BaseInterface> and directly convert it into the specific derived class type for further usage and to store in the DB, with the custom BaseInterfaceConverter. But that does not work like expected.
The following Code is my current state, i did not get it running - and for me it looks like it is because of the "nested" List inside the JSON that i'd like to deserialize. The Error in the Console is:
"Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray.
My code looks like this:
// The Parent Class
public class BaseInterface
{
public string Name { get; set; }
// This defines the specific interface-type
// TODO: change to an enumeration
public string InterfaceType { get; set; }
public string Option { get; set; }
}
// First Child
public class RestInterface : BaseInterface
{
public string DefaultBaseUri { get; set; }
}
// 2nd Child
public class ModbusTcpInterface: BaseInterface
{
public string IpAddress { get; set; }
public int Port { get; set; }
}
// The Configuration which holds a list of Interfaces that i would like to parse while deserializing.
public class DeviceConfiguration
{
public string DeviceName { get; set; }
public string DeviceManufacturer { get; set; }
[JsonConverter(typeof(BaseInterfaceConverter))]
public List<BaseInterface> SupportedInterfaces { get; set; }
//... more props here
}
public class BaseInterfaceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(BaseInterface);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
JObject jobject = JObject.Load(reader);
switch (jobject["InterfaceType"].Value<string>())
{
case "Rest":
return JsonConvert.DeserializeObject<RestInterface>(jobject.ToString());
case "ModbusTcp":
return JsonConvert.DeserializeObject<ModbusTcpInterface>(jobject.ToString());
default:
throw new ArgumentException(String.Format("The Interfacetype {0} is not supported!", jobject["InterfaceType"].Value<string>()));
}
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
What is that i'm missing? Can anyone please give me a hint?
Edit 1, added the JSON that I receive:
{
"_id": "1234",
"DeviceName": "First Configuration",
"DeviceManufacturer": "eeeitschi",
"SupportedInterfaces": [
{
"Name": "My first interface",
"InterfaceType": "Rest",
"Option": "option string here..",
"DefaultBaseUri": "mybaseurl.io/itsme",
},
{
"Name": "My second interface",
"InterfaceType": "ModbusTcp",
"Option": "option string here..",
"IpAddress": "127.0.0.1",
"Port": 502
},
{
"Name": "My third interface",
"InterfaceType": "Rest",
"Option": "option string here..",
"DefaultBaseUri": "base.url/api/devices",
},
]
}
Edit 2, rewrote the BaseInterfaceConverter after the answers from Max and Serge:
public class BaseInterfaceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(BaseInterface);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
JArray jarray = JArray.Load(reader);
return jarray.Select(entry => {
switch (entry["InterfaceType"].Value<string>())
{
case "Rest":
return JsonConvert.DeserializeObject<RestInterface>(entry.ToString());
case "ModbusTcp":
return JsonConvert.DeserializeObject<ModbusTcpInterface>(entry.ToString());
default:
throw new ArgumentException(String.Format("The Interfacetype {0} is not supported!", entry["InterfaceType"].Value<string>()));
}
}).ToList();
}
You can try this code
var jObject = JObject.Parse(json);
DeviceConfiguration deviceConfiguration = jObject.ToObject<DeviceConfiguration>();
deviceConfiguration.SupportedInterfaces = ((JArray)jObject["SupportedInterfaces"])
.Select(x => (string)x["InterfaceType"] == "Rest" ? (BaseInterface)x.ToObject<RestInterface>()
: (BaseInterface)x.ToObject<ModbusTcpInterface>())
.ToList();
or you can wrap it into converter
public class BaseClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<BaseInterface>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray jArray = JArray.Load(reader);
return jArray.Select(x => (string)x["InterfaceType"] == "Rest" ? (BaseInterface)x.ToObject<RestInterface>()
: (BaseInterface)x.ToObject<ModbusTcpInterface>()).ToList();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
but if you have an access to code that creates a json string, you can change it by using TypeNameHandling setting
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
var json = JsonConvert.SerializeObject(deviceConfiguration, jsonSerializerSettings);
In this case it will be much more simple code to deserialize
deviceConfiguration=JsonConvert.DeserializeObject<DeviceConfiguration>(json, jsonSerializerSettings);
You should be using JArray instead of JObject since SupportedInterfaces is an array not an object as the error says.
SupportedInterfaces looks like this in your JsonReader
[
{
"Name": "My first interface",
"InterfaceType": "Rest",
"Option": "option string here..",
"DefaultBaseUri": "mybaseurl.io/itsme",
},
{
"Name": "My second interface",
"InterfaceType": "ModbusTcp",
"Option": "option string here..",
"IpAddress": "127.0.0.1",
"Port": 502
},
{
"Name": "My third interface",
"InterfaceType": "Rest",
"Option": "option string here..",
"DefaultBaseUri": "base.url/api/devices",
},
]
This Imgur api call returns a list containing both Gallery Image and Gallery Album classes represented in JSON.
I can't see how to deserialize these automatically using Json.NET given that there is no $type property telling the deserializer which class is meant to be represented. There is a property called "IsAlbum" that can be used to differentiate between the two.
This question appears to show one method but it looks like a bit of a hack.
How do I go about deserializing these classes? (using C#, Json.NET).
Sample Data:
Gallery Image
{
"id": "OUHDm",
"title": "My most recent drawing. Spent over 100 hours.",
...
"is_album": false
}
Gallery Album
{
"id": "lDRB2",
"title": "Imgur Office",
...
"is_album": true,
"images_count": 3,
"images": [
{
"id": "24nLu",
...
"link": "http://i.imgur.com/24nLu.jpg"
},
{
"id": "Ziz25",
...
"link": "http://i.imgur.com/Ziz25.jpg"
},
{
"id": "9tzW6",
...
"link": "http://i.imgur.com/9tzW6.jpg"
}
]
}
}
You can do this fairly easily by creating a custom JsonConverter to handle the object instantiation. Assuming you have your classes defined something like this:
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
You would create the converter like this:
public class GalleryItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(GalleryItem).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
// Using a nullable bool here in case "is_album" is not present on an item
bool? isAlbum = (bool?)jo["is_album"];
GalleryItem item;
if (isAlbum.GetValueOrDefault())
{
item = new GalleryAlbum();
}
else
{
item = new GalleryImage();
}
serializer.Populate(jo.CreateReader(), item);
return item;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here's an example program showing the converter in action:
class Program
{
static void Main(string[] args)
{
string json = #"
[
{
""id"": ""OUHDm"",
""title"": ""My most recent drawing. Spent over 100 hours."",
""link"": ""http://i.imgur.com/OUHDm.jpg"",
""is_album"": false
},
{
""id"": ""lDRB2"",
""title"": ""Imgur Office"",
""link"": ""http://alanbox.imgur.com/a/lDRB2"",
""is_album"": true,
""images_count"": 3,
""images"": [
{
""id"": ""24nLu"",
""link"": ""http://i.imgur.com/24nLu.jpg""
},
{
""id"": ""Ziz25"",
""link"": ""http://i.imgur.com/Ziz25.jpg""
},
{
""id"": ""9tzW6"",
""link"": ""http://i.imgur.com/9tzW6.jpg""
}
]
}
]";
List<GalleryItem> items =
JsonConvert.DeserializeObject<List<GalleryItem>>(json,
new GalleryItemConverter());
foreach (GalleryItem item in items)
{
Console.WriteLine("id: " + item.id);
Console.WriteLine("title: " + item.title);
Console.WriteLine("link: " + item.link);
if (item.is_album)
{
GalleryAlbum album = (GalleryAlbum)item;
Console.WriteLine("album images (" + album.images_count + "):");
foreach (GalleryImage image in album.images)
{
Console.WriteLine(" id: " + image.id);
Console.WriteLine(" link: " + image.link);
}
}
Console.WriteLine();
}
}
}
And here is the output of the above program:
id: OUHDm
title: My most recent drawing. Spent over 100 hours.
link: http://i.imgur.com/OUHDm.jpg
id: lDRB2
title: Imgur Office
link: http://alanbox.imgur.com/a/lDRB2
album images (3):
id: 24nLu
link: http://i.imgur.com/24nLu.jpg
id: Ziz25
link: http://i.imgur.com/Ziz25.jpg
id: 9tzW6
link: http://i.imgur.com/9tzW6.jpg
Fiddle: https://dotnetfiddle.net/1kplME
Simply with JsonSubTypes attributes that work with Json.NET
[JsonConverter(typeof(JsonSubtypes), "is_album")]
[JsonSubtypes.KnownSubType(typeof(GalleryAlbum), true)]
[JsonSubtypes.KnownSubType(typeof(GalleryImage), false)]
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
Advanced to Brian Rogers answer. And about "use Serializer.Populate() instead of item.ToObject()".
If derived types has contstructors or some of their has own customconverter you must use general way for deserialize JSON.
So you must leave work for instantiate new object to NewtonJson. This way you can achieve it in you CustomJsonConverter:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
..... YOU Code For Determine Real Type of Json Record .......
// 1. Correct ContractResolver for you derived type
var contract = serializer.ContractResolver.ResolveContract(DeterminedType);
if (converter != null && !typeDeserializer.Type.IsAbstract && converter.GetType() == GetType())
{
contract.Converter = null; // Clean Wrong Converter grabbed by DefaultContractResolver from you base class for derived class
}
// Deserialize in general way
var jTokenReader = new JTokenReader(jObject);
var result = serializer.Deserialize(jTokenReader, DeterminedType);
return (result);
}
This work if you have recursion of objects.
I'm only posting this to clear up some of the confusion. If you are working with a predefined format and need to deserialize it, this is what I found worked best and demonstrates the mechanics so that others can tweak it as needed.
public class BaseClassConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var j = JObject.Load(reader);
var retval = BaseClass.From(j, serializer);
return retval;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override bool CanConvert(Type objectType)
{
// important - do not cause subclasses to go through this converter
return objectType == typeof(BaseClass);
}
}
// important to not use attribute otherwise you'll infinite loop
public abstract class BaseClass
{
internal static Type[] Types = new Type[] {
typeof(Subclass1),
typeof(Subclass2),
typeof(Subclass3)
};
internal static Dictionary<string, Type> TypesByName = Types.ToDictionary(t => t.Name.Split('.').Last());
// type property based off of class name
[JsonProperty(PropertyName = "type", Required = Required.Always)]
public string JsonObjectType { get { return this.GetType().Name.Split('.').Last(); } set { } }
// convenience method to deserialize a JObject
public static new BaseClass From(JObject obj, JsonSerializer serializer)
{
// this is our object type property
var str = (string)obj["type"];
// we map using a dictionary, but you can do whatever you want
var type = TypesByName[str];
// important to pass serializer (and its settings) along
return obj.ToObject(type, serializer) as BaseClass;
}
// convenience method for deserialization
public static BaseClass Deserialize(JsonReader reader)
{
JsonSerializer ser = new JsonSerializer();
// important to add converter here
ser.Converters.Add(new BaseClassConverter());
return ser.Deserialize<BaseClass>(reader);
}
}
Following implementation should let you de-serialize without changing the way you have designed your classes and by using a field other than $type to decide what to de-serialize it into.
public class GalleryImageConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(GalleryImage) || objectType == typeof(GalleryAlbum));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
if (!CanConvert(objectType))
throw new InvalidDataException("Invalid type of object");
JObject jo = JObject.Load(reader);
// following is to avoid use of magic strings
var isAlbumPropertyName = ((MemberExpression)((Expression<Func<GalleryImage, bool>>)(s => s.is_album)).Body).Member.Name;
JToken jt;
if (!jo.TryGetValue(isAlbumPropertyName, StringComparison.InvariantCultureIgnoreCase, out jt))
{
return jo.ToObject<GalleryImage>();
}
var propValue = jt.Value<bool>();
if(propValue) {
resultType = typeof(GalleryAlbum);
}
else{
resultType = typeof(GalleryImage);
}
var resultObject = Convert.ChangeType(Activator.CreateInstance(resultType), resultType);
var objectProperties=resultType.GetProperties();
foreach (var objectProperty in objectProperties)
{
var propType = objectProperty.PropertyType;
var propName = objectProperty.Name;
var token = jo.GetValue(propName, StringComparison.InvariantCultureIgnoreCase);
if (token != null)
{
objectProperty.SetValue(resultObject,token.ToObject(propType)?? objectProperty.GetValue(resultObject));
}
}
return resultObject;
}
catch (Exception ex)
{
throw;
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
#ИгорьОрлов's answer works for when you have types that can only be instantiated directly by JSON.net (due to [JsonConstructor] and/or use of [JsonProperty] directly on constructor parameters. However overwriting contract.Converter = null does not work when JSON.net has already cached the converter to use.
(This wouldn't be an issue if JSON.NET used immutable types to indicate when data and configuration is no-longer mutable, le sigh)
In my case, I did this:
Implemented a custom JsonConverter<T> (where T is my DTO's base class).
Defined a DefaultContractResolver subclass that overrides ResolveContractConverter to return my custom JsonConverter for only the base class.
In detail, and by example:
Supposing I have these immutable DTOs that represent a remote file-system (so there' DirectoryDto and FileDto which both inherit FileSystemDto, just like how DirectoryInfo and FileInfo derive from System.IO.FileSystemInfo):
public enum DtoKind
{
None = 0,
File,
Directory
}
public abstract class FileSystemDto
{
protected FileSystemDto( String name, DtoKind kind )
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.Kind = kind;
}
[JsonProperty( "name" )]
public String Name { get; }
[JsonProperty( "kind" )]
public String Kind { get; }
}
public class FileDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name" )] String name,
[JsonProperty("length")] Int64 length,
[JsonProperty("kind") ] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.File ) throw new InvalidOperationException( "blargh" );
this.Length = length;
}
[JsonProperty( "length" )]
public Int64 Length { get; }
}
public class DirectoryDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name")] String name,
[JsonProperty("kind")] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.Directory ) throw new InvalidOperationException( "blargh" );
}
}
Supposing I have a JSON array of FileSystemDto:
[
{ "name": "foo.txt", "kind": "File", "length": 12345 },
{ "name": "bar.txt", "kind": "File", "length": 12345 },
{ "name": "subdir", "kind": "Directory" },
]
I want Json.net to deserialize this to List<FileSystemDto>...
So define a subclass of DefaultContractResolver (or if you already have a resolver implementation then subclass (or compose) that) and override ResolveContractConverter:
public class MyContractResolver : DefaultContractResolver
{
protected override JsonConverter? ResolveContractConverter( Type objectType )
{
if( objectType == typeof(FileSystemDto) )
{
return MyJsonConverter.Instance;
}
else if( objectType == typeof(FileDto ) )
{
// use default
}
else if( objectType == typeof(DirectoryDto) )
{
// use default
}
return base.ResolveContractConverter( objectType );
}
}
Then implement MyJsonConverter:
public class MyJsonConverter : JsonConverter<FileSystemDto>
{
public static MyJsonConverter Instance { get; } = new MyJsonConverter();
private MyJsonConverter() {}
// TODO: Override `CanWrite => false` and `WriteJson { throw; }` if you like.
public override FileSystemDto? ReadJson( JsonReader reader, Type objectType, FileSystemDto? existingValue, Boolean hasExistingValue, JsonSerializer serializer )
{
if( reader.TokenType == JsonToken.Null ) return null;
if( objectType == typeof(FileSystemDto) )
{
JObject jsonObject = JObject.Load( reader );
if( jsonObject.Property( "kind" )?.Value is JValue jv && jv.Value is String kind )
{
if( kind == "File" )
{
return jsonObject.ToObject<FileDto>( serializer );
}
else if( kind == "Directory" )
{
return jsonObject.ToObject<DirectoryDto>( serializer );
}
}
}
return null; // or throw, depending on your strictness.
}
}
Then, to deserialize, use a JsonSerializer instance with the ContractResolver set correctly, for example:
public static IReadOnlyList<FileSystemDto> DeserializeFileSystemJsonArray( String json )
{
JsonSerializer jss = new JsonSerializer()
{
ContractResolver = new KuduDtoContractResolver()
};
using( StringReader strRdr = new StringReader( json ) )
using( JsonTextReader jsonRdr = new JsonTextReader( strRdr ) )
{
List<FileSystemDto>? list = jss.Deserialize< List<FileSystemDto> >( jsonRdr );
// TODO: Throw if `list` is null.
return list;
}
}
This Imgur api call returns a list containing both Gallery Image and Gallery Album classes represented in JSON.
I can't see how to deserialize these automatically using Json.NET given that there is no $type property telling the deserializer which class is meant to be represented. There is a property called "IsAlbum" that can be used to differentiate between the two.
This question appears to show one method but it looks like a bit of a hack.
How do I go about deserializing these classes? (using C#, Json.NET).
Sample Data:
Gallery Image
{
"id": "OUHDm",
"title": "My most recent drawing. Spent over 100 hours.",
...
"is_album": false
}
Gallery Album
{
"id": "lDRB2",
"title": "Imgur Office",
...
"is_album": true,
"images_count": 3,
"images": [
{
"id": "24nLu",
...
"link": "http://i.imgur.com/24nLu.jpg"
},
{
"id": "Ziz25",
...
"link": "http://i.imgur.com/Ziz25.jpg"
},
{
"id": "9tzW6",
...
"link": "http://i.imgur.com/9tzW6.jpg"
}
]
}
}
You can do this fairly easily by creating a custom JsonConverter to handle the object instantiation. Assuming you have your classes defined something like this:
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
You would create the converter like this:
public class GalleryItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(GalleryItem).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
// Using a nullable bool here in case "is_album" is not present on an item
bool? isAlbum = (bool?)jo["is_album"];
GalleryItem item;
if (isAlbum.GetValueOrDefault())
{
item = new GalleryAlbum();
}
else
{
item = new GalleryImage();
}
serializer.Populate(jo.CreateReader(), item);
return item;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here's an example program showing the converter in action:
class Program
{
static void Main(string[] args)
{
string json = #"
[
{
""id"": ""OUHDm"",
""title"": ""My most recent drawing. Spent over 100 hours."",
""link"": ""http://i.imgur.com/OUHDm.jpg"",
""is_album"": false
},
{
""id"": ""lDRB2"",
""title"": ""Imgur Office"",
""link"": ""http://alanbox.imgur.com/a/lDRB2"",
""is_album"": true,
""images_count"": 3,
""images"": [
{
""id"": ""24nLu"",
""link"": ""http://i.imgur.com/24nLu.jpg""
},
{
""id"": ""Ziz25"",
""link"": ""http://i.imgur.com/Ziz25.jpg""
},
{
""id"": ""9tzW6"",
""link"": ""http://i.imgur.com/9tzW6.jpg""
}
]
}
]";
List<GalleryItem> items =
JsonConvert.DeserializeObject<List<GalleryItem>>(json,
new GalleryItemConverter());
foreach (GalleryItem item in items)
{
Console.WriteLine("id: " + item.id);
Console.WriteLine("title: " + item.title);
Console.WriteLine("link: " + item.link);
if (item.is_album)
{
GalleryAlbum album = (GalleryAlbum)item;
Console.WriteLine("album images (" + album.images_count + "):");
foreach (GalleryImage image in album.images)
{
Console.WriteLine(" id: " + image.id);
Console.WriteLine(" link: " + image.link);
}
}
Console.WriteLine();
}
}
}
And here is the output of the above program:
id: OUHDm
title: My most recent drawing. Spent over 100 hours.
link: http://i.imgur.com/OUHDm.jpg
id: lDRB2
title: Imgur Office
link: http://alanbox.imgur.com/a/lDRB2
album images (3):
id: 24nLu
link: http://i.imgur.com/24nLu.jpg
id: Ziz25
link: http://i.imgur.com/Ziz25.jpg
id: 9tzW6
link: http://i.imgur.com/9tzW6.jpg
Fiddle: https://dotnetfiddle.net/1kplME
Simply with JsonSubTypes attributes that work with Json.NET
[JsonConverter(typeof(JsonSubtypes), "is_album")]
[JsonSubtypes.KnownSubType(typeof(GalleryAlbum), true)]
[JsonSubtypes.KnownSubType(typeof(GalleryImage), false)]
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
Advanced to Brian Rogers answer. And about "use Serializer.Populate() instead of item.ToObject()".
If derived types has contstructors or some of their has own customconverter you must use general way for deserialize JSON.
So you must leave work for instantiate new object to NewtonJson. This way you can achieve it in you CustomJsonConverter:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
..... YOU Code For Determine Real Type of Json Record .......
// 1. Correct ContractResolver for you derived type
var contract = serializer.ContractResolver.ResolveContract(DeterminedType);
if (converter != null && !typeDeserializer.Type.IsAbstract && converter.GetType() == GetType())
{
contract.Converter = null; // Clean Wrong Converter grabbed by DefaultContractResolver from you base class for derived class
}
// Deserialize in general way
var jTokenReader = new JTokenReader(jObject);
var result = serializer.Deserialize(jTokenReader, DeterminedType);
return (result);
}
This work if you have recursion of objects.
I'm only posting this to clear up some of the confusion. If you are working with a predefined format and need to deserialize it, this is what I found worked best and demonstrates the mechanics so that others can tweak it as needed.
public class BaseClassConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var j = JObject.Load(reader);
var retval = BaseClass.From(j, serializer);
return retval;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override bool CanConvert(Type objectType)
{
// important - do not cause subclasses to go through this converter
return objectType == typeof(BaseClass);
}
}
// important to not use attribute otherwise you'll infinite loop
public abstract class BaseClass
{
internal static Type[] Types = new Type[] {
typeof(Subclass1),
typeof(Subclass2),
typeof(Subclass3)
};
internal static Dictionary<string, Type> TypesByName = Types.ToDictionary(t => t.Name.Split('.').Last());
// type property based off of class name
[JsonProperty(PropertyName = "type", Required = Required.Always)]
public string JsonObjectType { get { return this.GetType().Name.Split('.').Last(); } set { } }
// convenience method to deserialize a JObject
public static new BaseClass From(JObject obj, JsonSerializer serializer)
{
// this is our object type property
var str = (string)obj["type"];
// we map using a dictionary, but you can do whatever you want
var type = TypesByName[str];
// important to pass serializer (and its settings) along
return obj.ToObject(type, serializer) as BaseClass;
}
// convenience method for deserialization
public static BaseClass Deserialize(JsonReader reader)
{
JsonSerializer ser = new JsonSerializer();
// important to add converter here
ser.Converters.Add(new BaseClassConverter());
return ser.Deserialize<BaseClass>(reader);
}
}
Following implementation should let you de-serialize without changing the way you have designed your classes and by using a field other than $type to decide what to de-serialize it into.
public class GalleryImageConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(GalleryImage) || objectType == typeof(GalleryAlbum));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
if (!CanConvert(objectType))
throw new InvalidDataException("Invalid type of object");
JObject jo = JObject.Load(reader);
// following is to avoid use of magic strings
var isAlbumPropertyName = ((MemberExpression)((Expression<Func<GalleryImage, bool>>)(s => s.is_album)).Body).Member.Name;
JToken jt;
if (!jo.TryGetValue(isAlbumPropertyName, StringComparison.InvariantCultureIgnoreCase, out jt))
{
return jo.ToObject<GalleryImage>();
}
var propValue = jt.Value<bool>();
if(propValue) {
resultType = typeof(GalleryAlbum);
}
else{
resultType = typeof(GalleryImage);
}
var resultObject = Convert.ChangeType(Activator.CreateInstance(resultType), resultType);
var objectProperties=resultType.GetProperties();
foreach (var objectProperty in objectProperties)
{
var propType = objectProperty.PropertyType;
var propName = objectProperty.Name;
var token = jo.GetValue(propName, StringComparison.InvariantCultureIgnoreCase);
if (token != null)
{
objectProperty.SetValue(resultObject,token.ToObject(propType)?? objectProperty.GetValue(resultObject));
}
}
return resultObject;
}
catch (Exception ex)
{
throw;
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
#ИгорьОрлов's answer works for when you have types that can only be instantiated directly by JSON.net (due to [JsonConstructor] and/or use of [JsonProperty] directly on constructor parameters. However overwriting contract.Converter = null does not work when JSON.net has already cached the converter to use.
(This wouldn't be an issue if JSON.NET used immutable types to indicate when data and configuration is no-longer mutable, le sigh)
In my case, I did this:
Implemented a custom JsonConverter<T> (where T is my DTO's base class).
Defined a DefaultContractResolver subclass that overrides ResolveContractConverter to return my custom JsonConverter for only the base class.
In detail, and by example:
Supposing I have these immutable DTOs that represent a remote file-system (so there' DirectoryDto and FileDto which both inherit FileSystemDto, just like how DirectoryInfo and FileInfo derive from System.IO.FileSystemInfo):
public enum DtoKind
{
None = 0,
File,
Directory
}
public abstract class FileSystemDto
{
protected FileSystemDto( String name, DtoKind kind )
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.Kind = kind;
}
[JsonProperty( "name" )]
public String Name { get; }
[JsonProperty( "kind" )]
public String Kind { get; }
}
public class FileDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name" )] String name,
[JsonProperty("length")] Int64 length,
[JsonProperty("kind") ] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.File ) throw new InvalidOperationException( "blargh" );
this.Length = length;
}
[JsonProperty( "length" )]
public Int64 Length { get; }
}
public class DirectoryDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name")] String name,
[JsonProperty("kind")] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.Directory ) throw new InvalidOperationException( "blargh" );
}
}
Supposing I have a JSON array of FileSystemDto:
[
{ "name": "foo.txt", "kind": "File", "length": 12345 },
{ "name": "bar.txt", "kind": "File", "length": 12345 },
{ "name": "subdir", "kind": "Directory" },
]
I want Json.net to deserialize this to List<FileSystemDto>...
So define a subclass of DefaultContractResolver (or if you already have a resolver implementation then subclass (or compose) that) and override ResolveContractConverter:
public class MyContractResolver : DefaultContractResolver
{
protected override JsonConverter? ResolveContractConverter( Type objectType )
{
if( objectType == typeof(FileSystemDto) )
{
return MyJsonConverter.Instance;
}
else if( objectType == typeof(FileDto ) )
{
// use default
}
else if( objectType == typeof(DirectoryDto) )
{
// use default
}
return base.ResolveContractConverter( objectType );
}
}
Then implement MyJsonConverter:
public class MyJsonConverter : JsonConverter<FileSystemDto>
{
public static MyJsonConverter Instance { get; } = new MyJsonConverter();
private MyJsonConverter() {}
// TODO: Override `CanWrite => false` and `WriteJson { throw; }` if you like.
public override FileSystemDto? ReadJson( JsonReader reader, Type objectType, FileSystemDto? existingValue, Boolean hasExistingValue, JsonSerializer serializer )
{
if( reader.TokenType == JsonToken.Null ) return null;
if( objectType == typeof(FileSystemDto) )
{
JObject jsonObject = JObject.Load( reader );
if( jsonObject.Property( "kind" )?.Value is JValue jv && jv.Value is String kind )
{
if( kind == "File" )
{
return jsonObject.ToObject<FileDto>( serializer );
}
else if( kind == "Directory" )
{
return jsonObject.ToObject<DirectoryDto>( serializer );
}
}
}
return null; // or throw, depending on your strictness.
}
}
Then, to deserialize, use a JsonSerializer instance with the ContractResolver set correctly, for example:
public static IReadOnlyList<FileSystemDto> DeserializeFileSystemJsonArray( String json )
{
JsonSerializer jss = new JsonSerializer()
{
ContractResolver = new KuduDtoContractResolver()
};
using( StringReader strRdr = new StringReader( json ) )
using( JsonTextReader jsonRdr = new JsonTextReader( strRdr ) )
{
List<FileSystemDto>? list = jss.Deserialize< List<FileSystemDto> >( jsonRdr );
// TODO: Throw if `list` is null.
return list;
}
}
I stumbled upon a service that outputs JSON in the following format:
{
"Author": "me",
"Version": "1.0.0",
"data.Type1": {
"Children": [
{
"data.Type1": {
"Children": [
{
"data.Type2": {
"name": "John",
"surname": "Doe"
}
}
]
}
},
{
"data.Type3": {
"dob": "1990-01-01"
}
}
]
}
}
Data type names are preserved as property names and their values are the actual objects. They all start with a data. prefix.
What I'd like to get afterwards is something like this:
{ // Root
"Author": "me",
"Version": "1.0.0",
"Children": [ // Type1
{
"Children": [ // Type1
{ // Type2
"Name": "John",
"Surname": "Doe"
}
]
},
{ // Type3
"DoB": "1990-01-01"
}
]
}
with the following classes:
class Type1 {
ICollection<object> Children { get; set; }
}
class Type2 {
public string Name { get; set; }
public string Surname { get; set; }
}
class Type3 {
public DateTime DoB { get; set; }
}
class Root
{
public string Author { get; set; }
public string Version { get; set; }
public Type1 Children { get; set; }
}
Question
How can I deserialize this into added C# classes, taking into account the data types and removing them from the tree?
I've tried with a custom JsonConverter, but am struggling with how to dynamically choose the converter, since the easiest way would be to put an attribute on the property, but it is not supported.
A small example would be great.
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
Not sure if this will work but have you tried using Newtonsoft.Json to serialize the object and include JsonProperty tags on the class properties? I know this will work when deserializing Json into a class.
<JsonProperty("user_id")>
Public Property UserID As String
//Converts Json {user_id: 123} to class.UserID = 123
To Serialize with Newtonsoft, first include Newtonsoft into the project then
Dim stringJson As String = Newtonsoft.Json.JsonConvert.SerializeObject(root)
This Imgur api call returns a list containing both Gallery Image and Gallery Album classes represented in JSON.
I can't see how to deserialize these automatically using Json.NET given that there is no $type property telling the deserializer which class is meant to be represented. There is a property called "IsAlbum" that can be used to differentiate between the two.
This question appears to show one method but it looks like a bit of a hack.
How do I go about deserializing these classes? (using C#, Json.NET).
Sample Data:
Gallery Image
{
"id": "OUHDm",
"title": "My most recent drawing. Spent over 100 hours.",
...
"is_album": false
}
Gallery Album
{
"id": "lDRB2",
"title": "Imgur Office",
...
"is_album": true,
"images_count": 3,
"images": [
{
"id": "24nLu",
...
"link": "http://i.imgur.com/24nLu.jpg"
},
{
"id": "Ziz25",
...
"link": "http://i.imgur.com/Ziz25.jpg"
},
{
"id": "9tzW6",
...
"link": "http://i.imgur.com/9tzW6.jpg"
}
]
}
}
You can do this fairly easily by creating a custom JsonConverter to handle the object instantiation. Assuming you have your classes defined something like this:
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
You would create the converter like this:
public class GalleryItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(GalleryItem).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
// Using a nullable bool here in case "is_album" is not present on an item
bool? isAlbum = (bool?)jo["is_album"];
GalleryItem item;
if (isAlbum.GetValueOrDefault())
{
item = new GalleryAlbum();
}
else
{
item = new GalleryImage();
}
serializer.Populate(jo.CreateReader(), item);
return item;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here's an example program showing the converter in action:
class Program
{
static void Main(string[] args)
{
string json = #"
[
{
""id"": ""OUHDm"",
""title"": ""My most recent drawing. Spent over 100 hours."",
""link"": ""http://i.imgur.com/OUHDm.jpg"",
""is_album"": false
},
{
""id"": ""lDRB2"",
""title"": ""Imgur Office"",
""link"": ""http://alanbox.imgur.com/a/lDRB2"",
""is_album"": true,
""images_count"": 3,
""images"": [
{
""id"": ""24nLu"",
""link"": ""http://i.imgur.com/24nLu.jpg""
},
{
""id"": ""Ziz25"",
""link"": ""http://i.imgur.com/Ziz25.jpg""
},
{
""id"": ""9tzW6"",
""link"": ""http://i.imgur.com/9tzW6.jpg""
}
]
}
]";
List<GalleryItem> items =
JsonConvert.DeserializeObject<List<GalleryItem>>(json,
new GalleryItemConverter());
foreach (GalleryItem item in items)
{
Console.WriteLine("id: " + item.id);
Console.WriteLine("title: " + item.title);
Console.WriteLine("link: " + item.link);
if (item.is_album)
{
GalleryAlbum album = (GalleryAlbum)item;
Console.WriteLine("album images (" + album.images_count + "):");
foreach (GalleryImage image in album.images)
{
Console.WriteLine(" id: " + image.id);
Console.WriteLine(" link: " + image.link);
}
}
Console.WriteLine();
}
}
}
And here is the output of the above program:
id: OUHDm
title: My most recent drawing. Spent over 100 hours.
link: http://i.imgur.com/OUHDm.jpg
id: lDRB2
title: Imgur Office
link: http://alanbox.imgur.com/a/lDRB2
album images (3):
id: 24nLu
link: http://i.imgur.com/24nLu.jpg
id: Ziz25
link: http://i.imgur.com/Ziz25.jpg
id: 9tzW6
link: http://i.imgur.com/9tzW6.jpg
Fiddle: https://dotnetfiddle.net/1kplME
Simply with JsonSubTypes attributes that work with Json.NET
[JsonConverter(typeof(JsonSubtypes), "is_album")]
[JsonSubtypes.KnownSubType(typeof(GalleryAlbum), true)]
[JsonSubtypes.KnownSubType(typeof(GalleryImage), false)]
public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}
public class GalleryImage : GalleryItem
{
// ...
}
public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}
Advanced to Brian Rogers answer. And about "use Serializer.Populate() instead of item.ToObject()".
If derived types has contstructors or some of their has own customconverter you must use general way for deserialize JSON.
So you must leave work for instantiate new object to NewtonJson. This way you can achieve it in you CustomJsonConverter:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
..... YOU Code For Determine Real Type of Json Record .......
// 1. Correct ContractResolver for you derived type
var contract = serializer.ContractResolver.ResolveContract(DeterminedType);
if (converter != null && !typeDeserializer.Type.IsAbstract && converter.GetType() == GetType())
{
contract.Converter = null; // Clean Wrong Converter grabbed by DefaultContractResolver from you base class for derived class
}
// Deserialize in general way
var jTokenReader = new JTokenReader(jObject);
var result = serializer.Deserialize(jTokenReader, DeterminedType);
return (result);
}
This work if you have recursion of objects.
I'm only posting this to clear up some of the confusion. If you are working with a predefined format and need to deserialize it, this is what I found worked best and demonstrates the mechanics so that others can tweak it as needed.
public class BaseClassConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var j = JObject.Load(reader);
var retval = BaseClass.From(j, serializer);
return retval;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override bool CanConvert(Type objectType)
{
// important - do not cause subclasses to go through this converter
return objectType == typeof(BaseClass);
}
}
// important to not use attribute otherwise you'll infinite loop
public abstract class BaseClass
{
internal static Type[] Types = new Type[] {
typeof(Subclass1),
typeof(Subclass2),
typeof(Subclass3)
};
internal static Dictionary<string, Type> TypesByName = Types.ToDictionary(t => t.Name.Split('.').Last());
// type property based off of class name
[JsonProperty(PropertyName = "type", Required = Required.Always)]
public string JsonObjectType { get { return this.GetType().Name.Split('.').Last(); } set { } }
// convenience method to deserialize a JObject
public static new BaseClass From(JObject obj, JsonSerializer serializer)
{
// this is our object type property
var str = (string)obj["type"];
// we map using a dictionary, but you can do whatever you want
var type = TypesByName[str];
// important to pass serializer (and its settings) along
return obj.ToObject(type, serializer) as BaseClass;
}
// convenience method for deserialization
public static BaseClass Deserialize(JsonReader reader)
{
JsonSerializer ser = new JsonSerializer();
// important to add converter here
ser.Converters.Add(new BaseClassConverter());
return ser.Deserialize<BaseClass>(reader);
}
}
Following implementation should let you de-serialize without changing the way you have designed your classes and by using a field other than $type to decide what to de-serialize it into.
public class GalleryImageConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(GalleryImage) || objectType == typeof(GalleryAlbum));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
if (!CanConvert(objectType))
throw new InvalidDataException("Invalid type of object");
JObject jo = JObject.Load(reader);
// following is to avoid use of magic strings
var isAlbumPropertyName = ((MemberExpression)((Expression<Func<GalleryImage, bool>>)(s => s.is_album)).Body).Member.Name;
JToken jt;
if (!jo.TryGetValue(isAlbumPropertyName, StringComparison.InvariantCultureIgnoreCase, out jt))
{
return jo.ToObject<GalleryImage>();
}
var propValue = jt.Value<bool>();
if(propValue) {
resultType = typeof(GalleryAlbum);
}
else{
resultType = typeof(GalleryImage);
}
var resultObject = Convert.ChangeType(Activator.CreateInstance(resultType), resultType);
var objectProperties=resultType.GetProperties();
foreach (var objectProperty in objectProperties)
{
var propType = objectProperty.PropertyType;
var propName = objectProperty.Name;
var token = jo.GetValue(propName, StringComparison.InvariantCultureIgnoreCase);
if (token != null)
{
objectProperty.SetValue(resultObject,token.ToObject(propType)?? objectProperty.GetValue(resultObject));
}
}
return resultObject;
}
catch (Exception ex)
{
throw;
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
#ИгорьОрлов's answer works for when you have types that can only be instantiated directly by JSON.net (due to [JsonConstructor] and/or use of [JsonProperty] directly on constructor parameters. However overwriting contract.Converter = null does not work when JSON.net has already cached the converter to use.
(This wouldn't be an issue if JSON.NET used immutable types to indicate when data and configuration is no-longer mutable, le sigh)
In my case, I did this:
Implemented a custom JsonConverter<T> (where T is my DTO's base class).
Defined a DefaultContractResolver subclass that overrides ResolveContractConverter to return my custom JsonConverter for only the base class.
In detail, and by example:
Supposing I have these immutable DTOs that represent a remote file-system (so there' DirectoryDto and FileDto which both inherit FileSystemDto, just like how DirectoryInfo and FileInfo derive from System.IO.FileSystemInfo):
public enum DtoKind
{
None = 0,
File,
Directory
}
public abstract class FileSystemDto
{
protected FileSystemDto( String name, DtoKind kind )
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.Kind = kind;
}
[JsonProperty( "name" )]
public String Name { get; }
[JsonProperty( "kind" )]
public String Kind { get; }
}
public class FileDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name" )] String name,
[JsonProperty("length")] Int64 length,
[JsonProperty("kind") ] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.File ) throw new InvalidOperationException( "blargh" );
this.Length = length;
}
[JsonProperty( "length" )]
public Int64 Length { get; }
}
public class DirectoryDto : FileSystemDto
{
[JsonConstructor]
public FileDto(
[JsonProperty("name")] String name,
[JsonProperty("kind")] DtoKind kind
)
: base( name: name, kind: kind )
{
if( kind != DtoKind.Directory ) throw new InvalidOperationException( "blargh" );
}
}
Supposing I have a JSON array of FileSystemDto:
[
{ "name": "foo.txt", "kind": "File", "length": 12345 },
{ "name": "bar.txt", "kind": "File", "length": 12345 },
{ "name": "subdir", "kind": "Directory" },
]
I want Json.net to deserialize this to List<FileSystemDto>...
So define a subclass of DefaultContractResolver (or if you already have a resolver implementation then subclass (or compose) that) and override ResolveContractConverter:
public class MyContractResolver : DefaultContractResolver
{
protected override JsonConverter? ResolveContractConverter( Type objectType )
{
if( objectType == typeof(FileSystemDto) )
{
return MyJsonConverter.Instance;
}
else if( objectType == typeof(FileDto ) )
{
// use default
}
else if( objectType == typeof(DirectoryDto) )
{
// use default
}
return base.ResolveContractConverter( objectType );
}
}
Then implement MyJsonConverter:
public class MyJsonConverter : JsonConverter<FileSystemDto>
{
public static MyJsonConverter Instance { get; } = new MyJsonConverter();
private MyJsonConverter() {}
// TODO: Override `CanWrite => false` and `WriteJson { throw; }` if you like.
public override FileSystemDto? ReadJson( JsonReader reader, Type objectType, FileSystemDto? existingValue, Boolean hasExistingValue, JsonSerializer serializer )
{
if( reader.TokenType == JsonToken.Null ) return null;
if( objectType == typeof(FileSystemDto) )
{
JObject jsonObject = JObject.Load( reader );
if( jsonObject.Property( "kind" )?.Value is JValue jv && jv.Value is String kind )
{
if( kind == "File" )
{
return jsonObject.ToObject<FileDto>( serializer );
}
else if( kind == "Directory" )
{
return jsonObject.ToObject<DirectoryDto>( serializer );
}
}
}
return null; // or throw, depending on your strictness.
}
}
Then, to deserialize, use a JsonSerializer instance with the ContractResolver set correctly, for example:
public static IReadOnlyList<FileSystemDto> DeserializeFileSystemJsonArray( String json )
{
JsonSerializer jss = new JsonSerializer()
{
ContractResolver = new KuduDtoContractResolver()
};
using( StringReader strRdr = new StringReader( json ) )
using( JsonTextReader jsonRdr = new JsonTextReader( strRdr ) )
{
List<FileSystemDto>? list = jss.Deserialize< List<FileSystemDto> >( jsonRdr );
// TODO: Throw if `list` is null.
return list;
}
}