JsonConvert deserialize an array of abstract classes - c#

Let's say I have the following class structure (Building is an abstract class):
public class Street
{
public string StreetName { get; set; }
public Building[] Buildings { get; set; }
}
public abstract class Building
{
public string Name { get; set; }
}
public class House : Building
{
public int Floors { get; set; }
}
public class Flat : Building
{
public int WhichFloor { get; set; }
}
I then create a street object with a few flats in the buildings array:
Flat f1 = new Flat { Name = "Flat 1", WhichFloor = 1 };
Flat f2 = new Flat { Name = "Flat 2", WhichFloor = 2 };
Street street = new Street
{
StreetName = "Street Name",
Buildings = new[] { f1, f2 }
};
Using JsonConvert I then Serialize the object:
var toJson = JsonConvert.SerializeObject(street);
Now I want to convert the json back to a street object:
var fromJson = JsonConvert.DeserializeObject<Street>(toJson);
This fails with the following error:
"Could not create an instance of type Building. Type is an interface or abstract class and cannot be instantiated. Path 'Buildings[0].WhichFloor'"
How can I tell the JsonConvert class that Buildings should be an array of flats?

As per #Evk's shared link, you should try setting TypeNameHandling to TypeNameHandling.Auto while serializing and deserializing:
var toJson = JsonConvert.SerializeObject(street, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
var fromJson = JsonConvert.DeserializeObject<Street>(toJson, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});

public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader,Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now implement this interface
public class SportActivityConverter : JsonCreationConverter<BaseSportActivity>
{
protected override BaseSportActivity Create(Type objectType, JObject jObject)
{
BaseSportActivity result = null;
try
{
switch ((ESportActivityType)jObject["activityType"].Value<int>())
{
case ESportActivityType.Football:
result = jObject.ToObject<FootballActivity>();
break;
case ESportActivityType.Basketball:
result = jObject.ToObject<BasketballActivity>();
break;
}
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
return result;
}
}

Related

How to deserialize an abstract class? [duplicate]

I am trying to deserialize a JSON string to a concrete class, which inherits from an abstract class, but I just can't get it working. I have googled and tried some solutions but they don't seem to work either.
This is what I have now:
abstract class AbstractClass { }
class ConcreteClass { }
public AbstractClass Decode(string jsonString)
{
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.TypeNameHandling = TypeNameHandling.All;
return (AbstractClass)JsonConvert.DeserializeObject(jsonString, null, jss);
}
However, if I try to cast the resulting object, it just doesn't work.
The reason why I don't use DeserializeObject is that I have many concrete classes.
Any suggestions?
I am using Newtonsoft.Json
One may not want to use TypeNameHandling (because one wants more compact json or wants to use a specific name for the type variable other than "$type"). Meanwhile, the customCreationConverter approach will not work if one wants to deserialize the base class into any of multiple derived classes without knowing which one to use in advance.
An alternative is to use an int or other type in the base class and define a JsonConverter.
[JsonConverter(typeof(BaseConverter))]
abstract class Base
{
public int ObjType { get; set; }
public int Id { get; set; }
}
class DerivedType1 : Base
{
public string Foo { get; set; }
}
class DerivedType2 : Base
{
public string Bar { get; set; }
}
The JsonConverter for the base class can then deserialize the object based on its type. The complication is that to avoid a stack overflow (where the JsonConverter repeatedly calls itself), a custom contract resolver must be used during this deserialization.
public class BaseSpecifiedConcreteClassConverter : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (typeof(Base).IsAssignableFrom(objectType) && !objectType.IsAbstract)
return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow)
return base.ResolveContractConverter(objectType);
}
}
public class BaseConverter : JsonConverter
{
static JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new BaseSpecifiedConcreteClassConverter() };
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Base));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
switch (jo["ObjType"].Value<int>())
{
case 1:
return JsonConvert.DeserializeObject<DerivedType1>(jo.ToString(), SpecifiedSubclassConversion);
case 2:
return JsonConvert.DeserializeObject<DerivedType2>(jo.ToString(), SpecifiedSubclassConversion);
default:
throw new Exception();
}
throw new NotImplementedException();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // won't be called because CanWrite returns false
}
}
That's it. Now you can use serialize/deserialize any derived class. You can also use the base class in other classes and serialize/deserialize those without any additional work:
class Holder
{
public List<Base> Objects { get; set; }
}
string json = #"
[
{
""Objects"" :
[
{ ""ObjType"": 1, ""Id"" : 1, ""Foo"" : ""One"" },
{ ""ObjType"": 1, ""Id"" : 2, ""Foo"" : ""Two"" },
]
},
{
""Objects"" :
[
{ ""ObjType"": 2, ""Id"" : 3, ""Bar"" : ""Three"" },
{ ""ObjType"": 2, ""Id"" : 4, ""Bar"" : ""Four"" },
]
},
]";
List<Holder> list = JsonConvert.DeserializeObject<List<Holder>>(json);
string serializedAgain = JsonConvert.SerializeObject(list);
Debug.WriteLine(serializedAgain);
I would suggest to use CustomCreationConverter in the following way:
public enum ClassDiscriminatorEnum
{
ChildClass1,
ChildClass2
}
public abstract class BaseClass
{
public abstract ClassDiscriminatorEnum Type { get; }
}
public class Child1 : BaseClass
{
public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass1;
public int ExtraProperty1 { get; set; }
}
public class Child2 : BaseClass
{
public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass2;
}
public class BaseClassConverter : CustomCreationConverter<BaseClass>
{
private ClassDiscriminatorEnum _currentObjectType;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jobj = JObject.ReadFrom(reader);
_currentObjectType = jobj["Type"].ToObject<ClassDiscriminatorEnum>();
return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
}
public override BaseClass Create(Type objectType)
{
switch (_currentObjectType)
{
case ClassDiscriminatorEnum.ChildClass1:
return new Child1();
case ClassDiscriminatorEnum.ChildClass2:
return new Child2();
default:
throw new NotImplementedException();
}
}
}
try something like this
public AbstractClass Decode(string jsonString)
{
var jss = new JavaScriptSerializer();
return jss.Deserialize<ConcreteClass>(jsonString);
}
UPDATE
for this scenario methinks all work as you want
public abstract class Base
{
public abstract int GetInt();
}
public class Der:Base
{
int g = 5;
public override int GetInt()
{
return g+2;
}
}
public class Der2 : Base
{
int i = 10;
public override int GetInt()
{
return i+17;
}
}
....
var jset = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All };
Base b = new Der()
string json = JsonConvert.SerializeObject(b, jset);
....
Base c = (Base)JsonConvert.DeserializeObject(json, jset);
where c type is test.Base {test.Der}
UPDATE
#Gusman suggest use TypeNameHandling.Objects instead of TypeNameHandling.All. It is enough and it will produce a less verbose serialization.
Actually, as it has been stated in an update, the simplest way (in 2019) is to use a simple custom pre-defined JsonSerializerSettings, as explained here
string jsonTypeNameAll = JsonConvert.SerializeObject(priceModels, Formatting.Indented,new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
And for deserializing :
TDSPriceModels models = JsonConvert.DeserializeObject<TDSPriceModels>(File.ReadAllText(jsonPath), new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
public class CustomConverter : JsonConverter
{
private static readonly JsonSerializer Serializer = new JsonSerializer();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var typeString = jObject.Value<string>("Kind"); //Kind is a property in json , from which we know type of child classes
var requiredType = RecoverType(typeString);
return Serializer.Deserialize(jObject.CreateReader(), requiredType);
}
private Type RecoverType(string typeString)
{
if (typeString.Equals(type of child class1, StringComparison.OrdinalIgnoreCase))
return typeof(childclass1);
if (typeString.Equals(type of child class2, StringComparison.OrdinalIgnoreCase))
return typeof(childclass2);
throw new ArgumentException("Unrecognized type");
}
public override bool CanConvert(Type objectType)
{
return typeof(Base class).IsAssignableFrom(objectType) || typeof((Base class) == objectType;
}
public override bool CanWrite { get { return false; } }
}
Now add this converter in JsonSerializerSettings as below
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
jsonSerializerSettings.Converters.Add(new CustomConverter());
After adding serialize or deserialize base class object as below
JsonConvert.DeserializeObject<Type>("json string", jsonSerializerSettings );
I had a similar issue, and I solved it with another way, maybe this would help someone:
I have json that contains in it several fields that are always the same, except for one field called "data" that can be a different type of class every time.
I would like to de-serialize it without analayzing every filed specific.
My solution is:
To define the main class (with 'Data' field) with , the field Data is type T.
Whenever that I de-serialize, I specify the type:
MainClass:
public class MainClass<T>
{
[JsonProperty("status")]
public Statuses Status { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("data")]
public T Data { get; set; }
public static MainClass<T> Parse(string mainClsTxt)
{
var response = JsonConvert.DeserializeObject<MainClass<T>>(mainClsTxt);
return response;
}
}
User
public class User
{
[JsonProperty("id")]
public int UserId { get; set; }
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
}
Product
public class Product
{
[JsonProperty("product_id")]
public int ProductId { get; set; }
[JsonProperty("product_name")]
public string ProductName { get; set; }
[JsonProperty("stock")]
public int Stock { get; set; }
}
Using
var v = MainClass<User>.Parse(userJson);
var v2 = MainClass<Product>.Parse(productJson);
json example
userJson: "{"status":1,"description":"my description","data":{"id":12161347,"first_name":"my fname","last_name":"my lname"}}"
productJson: "{"status":1,"description":"my description","data":{"product_id":5,"product_name":"my product","stock":1000}}"
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader,Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now implement this interface
public class SportActivityConverter : JsonCreationConverter<BaseSportActivity>
{
protected override BaseSportActivity Create(Type objectType, JObject jObject)
{
BaseSportActivity result = null;
try
{
switch ((ESportActivityType)jObject["activityType"].Value<int>())
{
case ESportActivityType.Football:
result = jObject.ToObject<FootballActivity>();
break;
case ESportActivityType.Basketball:
result = jObject.ToObject<BasketballActivity>();
break;
}
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
return result;
}
}

How to define string to object/dictionary in JSON

I create a class for define my request, I don't get the accepted JSON string
I define this object:
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
when I convert it to json, I get the following string:
{"requests":[{"var_args":[{"title":"Test","owner":"skaner"}]}]}
how can I define the class, for get the accepted json string:
{"requests":[{"var_args":[{"title":"Test"},{"owner":"skaner"}]}]}
You can write a custom JSON converter that can serialize every property of an object (of a known type) into a different JSON object.
public class PropertyAsObjectConverter : JsonConverter
{
private readonly Type[] _types;
public PropertyAsObjectConverter(params Type[] types)
{
_types = types;
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var name = property.Name;
var attrs = property.GetCustomAttributes(typeof(JsonPropertyAttribute));
if(attrs != null)
{
if (attrs.FirstOrDefault() is JsonPropertyAttribute attr)
name = attr.PropertyName;
}
writer.WriteStartObject();
writer.WritePropertyName(name);
serializer.Serialize(writer, property.GetValue(value));
writer.WriteEndObject();
}
}
}
This implements only the serialization, but you can extend it to support deserialization too. You can also extend it to serialize fields should you need that.
You can then define your classes as follows. Notice that I am using JsonPropertyAttribute here to specify the name in the serialized JSON.
public class Content
{
[JsonProperty("requests")]
public Request Value { get; set; }
}
public class Request
{
[JsonProperty("var_args")]
public VarArgs[] Arguments { get; set; }
}
public class VarArgs
{
[JsonProperty("title")]
public object Title { get; set; }
[JsonProperty("owner")]
public object Owner { get; set; }
}
This is how you can use it:
static void Main(string[] args)
{
var request = new Content()
{
Value = new Request()
{
Arguments = new VarArgs[]
{
new VarArgs()
{
Title = "Test",
Owner = "Skaner",
}
}
}
};
var text = JsonConvert.SerializeObject(
request,
Formatting.None,
new PropertyAsObjectConverter(typeof(VarArgs)));
Console.WriteLine(text);
}
The output for this sample is the one you expect:
{"requests":{"var_args":[{"title":"Test"},{"owner":"Skaner"}]}}
You could use a custom JsonConverter like the below.
It takes the Var_Args object and splits it in two different JObject which correspond to two different JSON objects.
public class VarArgsConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = (JObject)JToken.FromObject(value);
var objTitle = new JObject();
objTitle.Add("title", obj.GetValue("title"));
var objOwner = new JObject();
objOwner.Add("owner", obj.GetValue("owner"));
objTitle.WriteTo(writer);
objOwner.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Var_Args);
}
}
public class Wrapper
{
[JsonProperty("requests")]
public Request Requests { get; set; }
}
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
Then use it:
var wrapper = new Wrapper();
var request = new Request();
request.var_args = new Var_Args[] {
new Var_Args(){ title = "Test", owner = "skaner" },
new Var_Args(){ title = "Test2", owner = "skaner2" }
};
wrapper.Requests = request;
var serialized = JsonConvert.SerializeObject(wrapper, new VarArgsConverter());
Output
{"requests":{"var_args":[{"title":"Test"},{"owner":"skaner"},{"title":"Test2"},{"owner":"skaner2"}]}}
Note: I'm using the Wrapper class just to produce the requested JSON.
If you don't want to specify the converter each time, you can register your converter globally. Please see this answer which explains how you can do that. So, the serializer will use your custom JsonConverter every time you try to serialize a Var_Args object.
If you register the JsonConvert globally you can use:
var serialized = JsonConvert.SerializeObject(wrapper);
You can use System.Reflection to redefine Var_Args as an implementation of the IEnumerable<Dictionary<string,object>> interface by adding two methods to the class:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
var Properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Entry = new Dictionary<string,object>();
Entry.Add(Property.Name, Property.GetValue(this));
yield return Entry;
}
}
public object title { get; set; }
public object owner { get; set; }
}
While Reflection may be regarded as slow, there is a technique you can use to statically compile an IEnumerable at runtime so that the reflection only occurs once for the definition of the class, like this:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
private struct PropertyList<T>
{
public static readonly List<Func<T,Dictionary<string,object>>> PropertyGetters;
static PropertyList()
{
PropertyGetters = new List<Func<T,Dictionary<string,object>>>();
var Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Args = new [] { Expression.Parameter(typeof(T)) };
var Key = Property.Name;
var Value = Expression.Property(Args[0], Property);
Func<T,object> Get = Expression.Lambda<Func<T,object>>(Value, Args).Compile();
PropertyGetters.Add(obj =>
{
var entry = new Dictionary<string,object>();
entry.Add(Key, Get(obj));
return entry;
});
}
}
}
protected static IEnumerable<Dictionary<string,object>> GetPropertiesAsEntries<T>(T obj)
{
return PropertyList<T>.PropertyGetters.Select(f => f(obj));
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
return GetPropertiesAsEntries(this).GetEnumerator();
}
public object title { get; set; }
public object owner { get; set; }
}

Custom object serialization

Is there a way to change how an object is serialized in json? for example, the following object:
class PersonName {
public string Name { get; set; }
}
var obj = new {
pName = new PersonName {
Name = "Andrew"
}
}
is serialized as {"pName": { "name": "Andrew" }}.
But is there a way to serialize it as {"pName": "Andrew"}?
This should work:
class PersonName
{
[JsonProperty(PropertyName = "pName")]
public string Name { get; set; }
}
var obj = new PersonName { Name = "Andrew" }
So finally I've managed to get the answer to this one.
A custom converter is needed:
public class PersonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var person = value as PersonName;
if (person == null)
{
return;
}
serializer.Serialize(writer, person.Name);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var constructorInfo = objectType.GetTypeInfo().GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();
var parameterInfo = constructorInfo.GetParameters().Single();
var parameterType = parameterInfo.ParameterType;
var value = serializer.Deserialize(reader, parameterType);
return Activator.CreateInstance(objectType, new[] { value });
}
public override bool CanConvert(Type objectType)
{
return typeof(PersonName).GetTypeInfo().IsAssignableFrom(objectType);
}
}
which can be used like this:
[JsonConverter(typeof(PersonConverter))]
public class PersonName
{
public PersonName(string name)
{
Name = name;
}
public string Name { get; set; }
}

How to serialize object to json with type info using Newtonsoft.Json?

I want to have a property with type name in JSON when I serialize objects of certain types.
I wrote a converter:
public class TypeInfoConverter : JsonConverter {
private readonly IEnumerable<Type> _types;
public TypeInfoConverter(IEnumerable<Type> types) {
Contract.Requires(types != null);
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
var jObject = JObject.FromObject(value, serializer);
jObject.AddFirst(new JProperty("Type", value.GetType().Name));
jObject.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
return serializer.Deserialize(reader, objectType);
}
public override bool CanConvert(Type objectType) {
return _types.Any(t => t.IsAssignableFrom(objectType));
}
}
But when I'm trying to serialize object I have an infinity recursion here:
var jObject = JObject.FromObject(value, serializer); It is obvious because I use the same instance of JsonSerializer which was configured with that converter.
How to prevent using this converter, but I want to use other converters which configured for this serializer?
Types which I want to serialize:
public interface ITaskResult {
}
public class UserHasRejectedOffer : ITaskResult {
public string Message { get; set; }
}
public class UserHasFilledForm : ITaskResult {
public string FormValue1 { get; set; }
public string Formvalue2 { get; set; }
}
...
var jsonSerializerSettings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
};
var json = JsonConvert.SerializeObject(instance, jsonSerializerSettings);
http://james.newtonking.com/json/help/index.html?topic=html/SerializationSettings.htm
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var converters = serializer.Converters.Where(x => !(x is TypeInfoConverter)).ToArray();
var jObject = JObject.FromObject(value);
jObject.AddFirst(new JProperty("Type", value.GetType().Name));
jObject.WriteTo(writer, converters);
}
Have you tried creating a new instance of JsonSerializer, then copying all the converters from the original serializer except the converter that causes the infinite recursion?
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = serializer.Converters.Where(s => !(s is TypeInfoConverter)).ToList()
// also copy any other custom settings from the serializer you wish to pass through
DateFormatHandling = serializer.DateFormatHandling,
MissingMemberHandling = serializer.MissingMemberHandling,
NullValueHandling = serializer.NullValueHandling,
Formatting = serializer.Formatting
};
var localSerializer = JsonSerializer.Create(settings);
var jObject = JObject.FromObject(value, localSerializer);
jObject.AddFirst(new JProperty("Type", value.GetType().Name));
jObject.WriteTo(writer);
}
#CalvinDale shows how to serialize object, but the main chalenge is how to deserialize back an object with type info. This is an example how to do this. For this example we have an array of 3 different classes objects that contain the same interface
IAnimal[] animals = new IAnimal[] {
new Cat{CatName="Tom"},
new Dog{DogName="Scoopy"},
new Rabbit{RabitName="Honey"}
};
classes
public class Cat : IAnimal { public string CatName { get; set; } }
public class Dog : IAnimal { public string DogName { get; set; } }
public class Rabbit : IAnimal { public string RabitName { get; set; } }
public interface IAnimal { }
Code to serialize
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All
};
var json = JsonConvert.SerializeObject(animals, jsonSerializerSettings);
here is the most difficult part - how to deserialize back
List<IAnimal> animalsBack = ((JArray)JsonConvert.DeserializeObject(json))
.Select(o => (IAnimal)JsonConvert.DeserializeObject(o.ToString(),
Type.GetType((string)o["$type"]))).ToList();
Test
json = JsonConvert.SerializeObject(animalsBack, Newtonsoft.Json.Formatting.Indented);
test result
[
{
"CatName": "Tom"
},
{
"DogName": "Scoopy"
},
{
"RabitName": "Honey"
}
]

C#: Deserializing JSON when one field can be different types

I am communicating with an API that returns JSON containing either true, false or an array of string arrays. I wish to deserialize this JSON and store the boolean value, if there is one, in a class field called Success of data type bool, and the array, if there is one, in a field called Result of a custom data type.
What is the best to go about achieving this?
Some JSON:
[
{"status":"ok","result":true},
{
"status":"ok",
"result":
[
{"name":"John Doe","UserIdentifier":"abc","MaxAccounts":"2"}
]
}
]
My Result class:
class Result
{
string Name,
string UserIdentifier,
string MaxAccounts
}
Class for communicating with the Api:
class Api
{
public string Status { get; set; }
public Result[] Result { get; set; }
public bool Success { get; set; }
}
With JSON.NET you could write a custom JSON converter. For example you could have the following objects:
public class Root
{
public string Status { get; set; }
public Result Result { get; set; }
}
public class Result
{
public bool? Value { get; set; }
public Item[] Items { get; set; }
}
public class Item
{
public string Name { get; set; }
public string UserIdentifier { get; set; }
public string MaxAccounts { get; set; }
}
and your JSON will be deserialized to a Root[].
Here's how the custom JSON converter may look like:
public class ResultConverter: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Result);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Boolean)
{
return new Result
{
Value = (bool)reader.Value,
};
}
return new Result
{
Items = serializer.Deserialize<Item[]>(reader),
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// If you want to support serializing you could implement this method as well
throw new NotImplementedException();
}
}
and then:
class Program
{
static void Main()
{
var json =
#"[
{
""status"": ""ok"",
""result"": true
},
{
""status"": ""ok"",
""result"": [
{
""name"": ""John Doe"",
""UserIdentifier"": ""abc"",
""MaxAccounts"": ""2""
}
]
}
]";
var settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Root[] root = JsonConvert.DeserializeObject<Root[]>(json, settings);
// do something with the results here
}
}
create and arraylist using Api and Results objects. I have just tried this and it works.
var api = new Api();
var result = new Result();
api.Status = "ok";
api.Success = true;
result.Name = "John Doe";
result.UserIdentifier = "abc";
result.MaxAccounts = "2";
api.Result = new Result[1];
api.Result[0] = result;
var arrayList = new ArrayList() { new {api.Status, api.Success},
new { api.Status, api.Result} };
var json = JsonConvert.SerializeObject(arrayList, Formatting.Indented);

Categories