How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
I am trying to extend the JSON.net example given here http://james.newtonking.com/projects/json/help/CustomCreationConverter.html
I have another sub class deriving from base class/Interface
public class Person { public string FirstName { get; set; } public string LastName { get; set; } } public class Employee : Person { public string Department { get; set; } public string JobTitle { get; set; } } public class Artist : Person { public string Skill { get; set; } } List people = new List { new Employee(), new Employee(), new Artist(), };
How do I deserialize following Json back to List< Person >
[ { "Department": "Department1", "JobTitle": "JobTitle1", "FirstName": "FirstName1", "LastName": "LastName1" }, { "Department": "Department2", "JobTitle": "JobTitle2", "FirstName": "FirstName2", "LastName": "LastName2" }, { "Skill": "Painter", "FirstName": "FirstName3", "LastName": "LastName3" } ]
I don't want to use TypeNameHandling JsonSerializerSettings. I am specifically looking for custom JsonConverter implementation to handle this. The documentation and examples around this are pretty sparse on the net. I can't seem to get the the overridden ReadJson() method implementation in JsonConverter right.
Answer by jdavies for How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
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.
What's required is to subclass JsonConverter
, overriding the ReadJson
method and creating a new abstract Create
method which acceps 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 existance 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 persons = JsonConvert.DeserializeObject>(json, new PersonConverter()); ... public class PersonConverter : JsonCreationConverter { 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 : JsonConverter { /// /// Create an instance of objectType, based properties in the JSON object /// /// type of object expected /// /// contents of JSON object that will be deserialized /// /// protected abstract T Create(Type objectType, JObject jObject); public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } 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; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
Answer by totem for How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
Just thought i would share a solution also based on this that works with the Knowntype attribute using reflection , had to get derived class from any base class, solution can benefit from recursion to find the best matching class though i didn't need it in my case, matching is done by the type given to the converter if it has KnownTypes it will scan them all until it matches a type that has all the properties inside the json string, first one to match will be chosen.
usage is as simple as:
string json = "{ Name:\"Something\", LastName:\"Otherthing\" }"; var ret = JsonConvert.DeserializeObject(json, new KnownTypeConverter());
in the above case ret will be of type B.
JSON classes:
[KnownType(typeof(B))] public class A { public string Name { get; set; } } public class B : A { public string LastName { get; set; } }
Converter code:
/// /// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer /// Selected class will be the first class to match all properties in the json object. /// public class KnownTypeConverter : JsonConverter { public override bool CanConvert(Type objectType) { return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute); } 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 System.Attribute[] attrs = System.Attribute.GetCustomAttributes(objectType); // Reflection. // Displaying output. foreach (System.Attribute attr in attrs) { if (attr is KnownTypeAttribute) { KnownTypeAttribute k = (KnownTypeAttribute) attr; var props = k.Type.GetProperties(); bool found = true; foreach (var f in jObject) { if (!props.Any(z => z.Name == f.Key)) { found = false; break; } } if (found) { var target = Activator.CreateInstance(k.Type); serializer.Populate(jObject.CreateReader(),target); return target; } } } throw new ObjectNotFoundException(); // Populate the object properties } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
Answer by Alain for How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
The above solution for the JsonCreationConverter
is all over the internet, but has a flaw that manifests itself in rare occasions. The new JsonReader created in the ReadJson method does not inherit any of the original reader's configuration values (Culture, DateParseHandling, DateTimeZoneHandling, FloatParseHandling, etc...). These values should be copied over before using the new JsonReader in serializer.Populate().
This is the best I could come up with to fix some of the problems with the above implementation, but I still think there are some things being overlooked:
Update I updated this to have a more explicit method that makes a copy of an existing reader. This just encapsulates the process of copying over individual JsonReader settings. Ideally this function would be maintained in the Newtonsoft library itself, but for now, you can use the following:
/// Creates a new reader for the specified jObject by copying the settings /// from an existing reader. /// The reader whose settings should be copied. /// The jObject to create a new reader for. /// The new disposable reader. public static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject) { JsonReader jObjectReader = jObject.CreateReader(); jObjectReader.Culture = reader.Culture; jObjectReader.DateFormatString = reader.DateFormatString; jObjectReader.DateParseHandling = reader.DateParseHandling; jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling; jObjectReader.FloatParseHandling = reader.FloatParseHandling; jObjectReader.MaxDepth = reader.MaxDepth; jObjectReader.SupportMultipleContent = reader.SupportMultipleContent; return jObjectReader; }
This should be used as follows:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; // Load JObject from stream JObject jObject = JObject.Load(reader); // Create target object based on JObject T target = Create(objectType, jObject); // Populate the object properties using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject)) { serializer.Populate(jObjectReader, target); } return
Older solution follows:
/// Base Generic JSON Converter that can help quickly define converters for specific types by automatically /// generating the CanConvert, ReadJson, and WriteJson methods, requiring the implementer only to define a strongly typed Create method. public abstract class JsonCreationConverter : JsonConverter { /// Create an instance of objectType, based properties in the JSON object /// type of object expected /// contents of JSON object that will be deserialized protected abstract T Create(Type objectType, JObject jObject); /// Determines if this converted is designed to deserialization to objects of the specified type. /// The target type for deserialization. /// True if the type is supported. public override bool CanConvert(Type objectType) { // FrameWork 4.5 // return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); // Otherwise return typeof(T).IsAssignableFrom(objectType); } /// Parses the json to the specified type. /// Newtonsoft.Json.JsonReader /// Target type. /// Ignored /// Newtonsoft.Json.JsonSerializer to use. /// Deserialized Object public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; // Load JObject from stream JObject jObject = JObject.Load(reader); // Create target object based on JObject T target = Create(objectType, jObject); //Create a new reader for this jObject, and set all properties to match the original reader. JsonReader jObjectReader = jObject.CreateReader(); jObjectReader.Culture = reader.Culture; jObjectReader.DateParseHandling = reader.DateParseHandling; jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling; jObjectReader.FloatParseHandling = reader.FloatParseHandling; // Populate the object properties serializer.Populate(jObjectReader, target); return target; } /// Serializes to the specified type /// Newtonsoft.Json.JsonWriter /// Object to serialize. /// Newtonsoft.Json.JsonSerializer to use. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } }
Answer by Alain for How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
Here's another solution that avoids the use of jObject.CreateReader()
, and instead creates a new JsonTextReader
(which is the behavior used by the default JsonCreate.Deserialze
method:
public abstract class JsonCreationConverter : JsonConverter { protected abstract T Create(Type objectType, JObject jObject); public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; // Load JObject from stream JObject jObject = JObject.Load(reader); // Create target object based on JObject T target = Create(objectType, jObject); // Populate the object properties StringWriter writer = new StringWriter(); serializer.Serialize(writer, jObject); using (JsonTextReader newReader = new JsonTextReader(new StringReader(writer.ToString()))) { newReader.Culture = reader.Culture; newReader.DateParseHandling = reader.DateParseHandling; newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling; newReader.FloatParseHandling = reader.FloatParseHandling; serializer.Populate(newReader, target); } return target; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } }
Answer by zlangner for How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
This is an expansion to totem's answer. It does basically the same thing but the property matching is based on the serialized json object, not reflect the .net object. This is important if you're using [JsonProperty], using the CamelCasePropertyNamesContractResolver, or doing anything else that will cause the json to not match the .net object.
Usage is simple:
[KnownType(typeof(B))] public class A { public string Name { get; set; } } public class B : A { public string LastName { get; set; } }
Converter code:
/// /// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer /// Selected class will be the first class to match all properties in the json object. /// public class KnownTypeConverter : JsonConverter { public override bool CanConvert( Type objectType ) { return System.Attribute.GetCustomAttributes( objectType ).Any( v => v is KnownTypeAttribute ); } 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 System.Attribute[ ] attrs = System.Attribute.GetCustomAttributes( objectType ); // Reflection. // check known types for a match. foreach( var attr in attrs.OfType( ) ) { object target = Activator.CreateInstance( attr.Type ); JObject jTest; using( var writer = new StringWriter( ) ) { using( var jsonWriter = new JsonTextWriter( writer ) ) { serializer.Serialize( jsonWriter, target ); string json = writer.ToString( ); jTest = JObject.Parse( json ); } } var jO = this.GetKeys( jObject ).Select( k => k.Key ).ToList( ); var jT = this.GetKeys( jTest ).Select( k => k.Key ).ToList( ); if( jO.Count == jT.Count && jO.Intersect( jT ).Count( ) == jO.Count ) { serializer.Populate( jObject.CreateReader( ), target ); return target; } } throw new SerializationException( string.Format( "Could not convert base class {0}", objectType ) ); } public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) { throw new NotImplementedException( ); } private IEnumerable> GetKeys( JObject obj ) { var list = new List>( ); foreach( var t in obj ) { list.Add( t ); } return list; } }
Answer by Denis Pitcher for How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
As another variation on Totem's known type solution, you can use reflection to create a generic type resolver to avoid the need to use known type attributes.
This uses a technique similar to Juval Lowy's GenericResolver for WCF.
As long as your base class is abstract or an interface, the known types will be automatically determined rather than having to be decorated with known type attributes.
In my own case I opted to use a $type property to designate type in my json object rather than try to determine it from the properties, though you could borrow from other solutions here to use property based determination.
public class JsonKnownTypeConverter : JsonConverter { public IEnumerable KnownTypes { get; set; } public JsonKnownTypeConverter() : this(ReflectTypes()) { } public JsonKnownTypeConverter(IEnumerable knownTypes) { KnownTypes = knownTypes; } protected object Create(Type objectType, JObject jObject) { if (jObject["$type"] != null) { string typeName = jObject["$type"].ToString(); return Activator.CreateInstance(KnownTypes.First(x => typeName == x.Name)); } else { return Activator.CreateInstance(objectType); } throw new InvalidOperationException("No supported type"); } public override bool CanConvert(Type objectType) { if (KnownTypes == null) return false; return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom); } 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 var target = Create(objectType, jObject); // Populate the object properties serializer.Populate(jObject.CreateReader(), target); return target; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } //Static helpers static Assembly CallingAssembly = Assembly.GetCallingAssembly(); static Type[] ReflectTypes() { List types = new List(); var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies(); foreach (var assemblyName in referencedAssemblies) { Assembly assembly = Assembly.Load(assemblyName); Type[] typesInReferencedAssembly = GetTypes(assembly); types.AddRange(typesInReferencedAssembly); } return types.ToArray(); } static Type[] GetTypes(Assembly assembly, bool publicOnly = true) { Type[] allTypes = assembly.GetTypes(); List types = new List(); foreach (Type type in allTypes) { if (type.IsEnum == false && type.IsInterface == false && type.IsGenericTypeDefinition == false) { if (publicOnly == true && type.IsPublic == false) { if (type.IsNested == false) { continue; } if (type.IsNestedPrivate == true) { continue; } } types.Add(type); } } return types.ToArray(); }
It can then be installed as a formatter
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonKnownTypeConverter());
Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72
0 comments:
Post a Comment