The following is a model based on the response from an API (using sample data):
public class SchoolInfoModel
{
public string Name { get; set; }
public string Website { get; set; }
public Address Address { get; set; }
public List<SchoolTypeModel> SchoolTypes { get; set; }
}
The SchoolTypeModel is where I'm stuck. The SchoolType will return a list but the content of the list might contain one other model or two other models or more. Basically a list of a number of different models. But I do not know in advance which models I receive. How do I map these?
Examples of "SchoolType" models I can receive:
public class HighSchoolModel
{
public string Type { get; set; }
public string SubName { get; set; }
public bool BA { get; set; }
public bool CP { get; set; }
public bool HU { get; set; }
public bool MN { get; set; }
public bool TI { get; set; }
}
public class SpecialPurposeSchoolModel
{
public string Type { get; set; }
public string SubName { get; set; }
public bool AH { get; set; }
}
I have a total of about 10 different types of school.
Here's a way you can achieve this with JSON.Net (which means it will work in .NET Framework 4.8). You can create a custom converter to figure out what properties exist in this JSON and use that to determine which type to deserialise to. For example:
public class SchoolTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
=> objectType == typeof(SchoolTypeModel);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (obj.ContainsKey("AH"))
{
return obj.ToObject<SpecialPurposeSchoolModel>(serializer);
}
if (obj.ContainsKey("BA"))
{
return obj.ToObject<HighSchoolModel>(serializer);
}
// We have no idea what this is, so panic
throw new Exception("No idea what to do with this value!");
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
// We are only using this to read JSON
throw new NotImplementedException();
}
}
And to read the JSON:
var settings = new JsonSerializerSettings();
settings.Converters.Add(new SchoolTypeConverter());
var result = JsonConvert.DeserializeObject<SchoolInfoModel>(yourJsonString,
settings); // Pass in the settings object we created above
API retuned json object like below 2 forms.
Form 1
{
"Pricing": [
{
"total": 27,
"currency": "USD",
"charges": [ //Chargers Array
{
"code": "C1",
"currency": "USD",
"rate": 15
},
{
"code": "C45",
"currency": "USD",
"rate": 12
}
]
}
]
}
Form 2
{
"Pricing": [
{
"total": 12,
"currency": "USD",
"charges": { //Chargers single object
"code": "C1",
"currency": "USD",
"rate": 12
}
}
]
}
As you can see sometime chargers object return with array and some times not. My question is how to parse this to C# class object? If I added the C# class like below it cannot be parse properly for Form 2. (Form 1 parsing properly)
public class Charge
{
public string code { get; set; }
public string currency { get; set; }
public decimal rate { get; set; }
}
public class Pricing
{
public decimal total { get; set; }
public string currency { get; set; }
public List<Charge> charges { get; set; } //In Form 2, this should be single object
}
public class MainObj
{
public List<Pricing> Pricing { get; set; }
}
Error occurred when parse with Newtonsoft deserialization.
MainObj obj = JsonConvert.DeserializeObject<MainObj>(json);
Error
Cannot deserialize the current JSON object (e.g. {"name":"value"})
into type 'System.Collections.Generic.List`1[Charge]' because the type
requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix
this error either change the JSON to a JSON array (e.g. [1,2,3]) or
change the deserialized type so that it is a normal .NET type (e.g.
not a primitive type like integer, not a collection type like an array
or List) that can be deserialized from a JSON object.
JsonObjectAttribute can also be added to the type to force it to
deserialize from a JSON object. Path 'Pricing[0].charges.code', line
1, position 69.
Any common method for parsing, when receiving different type of object types with C#?
(I look into this as well but it's for java. And most of this kind of question raised for java but not C#.)
Yet another way of dealing with this problem is to define a custom JsonConverter which can handle both cases.
class ArrayOrObjectConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return token.Type == JTokenType.Array
? token.ToObject<List<T>>()
: new List<T> { token.ToObject<T>() };
}
public override bool CanConvert(Type objectType)
=> objectType == typeof(List<T>);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=>throw new NotImplementedException();
}
Inside the ReadJson first we get a JToken to be able to determine the read value's Type (kind)
Based on that the we can either call ToObject<List<T>> or ToObject<T>
Inside the CanConvert we examine that the to be populated property's type is a List<T>
Even though there is a generic JsonConverter<T> where you don't have to define the CanConvert, its ReadJson can be implemented in a bit more complicated way
Since the question is all about deserialization I've not implemented the WriteJson method
You might also consider to override the CanWrite property of the base class to always return false
With this class in our hand you can decorate your properties with a JsonConverterAttribute to tell to the Json.NET how to deal with those properties
public class Pricing
{
public decimal total { get; set; }
public string currency { get; set; }
[JsonConverter(typeof(ArrayOrObjectConverter<Charge>))]
public List<Charge> charges { get; set; }
...
}
You could go for an approach to try and parse Form1's object to json, if it fails it will use Form2's object to json.
Example here and below: https://dotnetfiddle.net/F1Yh25
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Program
{
public static void Main()
{
string form1 = "{\"Pricing\":[{\"total\":27,\"currency\":\"USD\",\"charges\":[{\"code\":\"C1\",\"currency\":\"USD\",\"rate\":15},{\"code\":\"C45\",\"currency\":\"USD\",\"rate\":12}]}]}";
string form2 = "{\"Pricing\":[{\"total\":12,\"currency\":\"USD\",\"charges\":{\"code\":\"C1\",\"currency\":\"USD\",\"rate\":12}}]}";
string json = form1;//Change form1 to form2 and it will also work
try
{
Form1.Root Object = JsonConvert.DeserializeObject<Form1.Root>(json);
Console.WriteLine("Item 1's Pricing: " + Object.Pricing[0].total);
}
catch//If Form1's json is Form2 it will catch here
{
Form2.Root Object = JsonConvert.DeserializeObject<Form2.Root>(json);
Console.WriteLine("Item 1's Pricing: " + Object.Pricing[0].total);
}
}
public class Form1
{
public class Charge
{
public string code { get; set; }
public string currency { get; set; }
public int rate { get; set; }
}
public class Pricing
{
public int total { get; set; }
public string currency { get; set; }
public List<Charge> charges { get; set; }
}
public class Root
{
public List<Pricing> Pricing { get; set; }
}
}
public class Form2
{
public class Charges
{
public string code { get; set; }
public string currency { get; set; }
public int rate { get; set; }
}
public class Pricing
{
public int total { get; set; }
public string currency { get; set; }
public Charges charges { get; set; }
}
public class Root
{
public List<Pricing> Pricing { get; set; }
}
}
}
Ok so here is another way to do this without having to use two classes and not having a try catch. Basically just update Pricing class to following and it works for both cases. Probably a better way but this is better (at least in my opinion) and having two classes and having try catch do your branching. If you had ten properties with this issue would you then create 10! classes to handle every combo? No way lol!
public class Pricing {
public int total { get; set; }
public string currency { get; set; }
private List<Charge> _charges;
public object charges {
get {
return _charges;
}
set {
if(value.GetType().Name == "JArray") {
_charges = JsonConvert.DeserializeObject<List<Charge>>(value.ToString());
}
else {
_charges = new List<Charge>();
var charge = JsonConvert.DeserializeObject<Charge>(value.ToString());
_charges.Add(charge);
}
}
}
}
This question originates from another topic which can be found at:
Extract objects from JSON array to list
The thing is that I'm receiving the following JSON response, and my JSON.NET deserializer doesn't understand it, but several other JSON validators like https://jsonlint.com say that it is valid.
[
{"value":"{\"code\":\"MO\",\"description\":\"Monday\",\"isSet\":false}","nr":1}
,{"value":"{\"code\":\"TU\",\"description\":\"Tuesday\",\"isSet\":true}","nr":2}
]
In my opinion the problem here is that the value object looks like a JSON object, but actually is a string.
JsonConvert.DeserializeObject keeps throwing errors until I remove the additional quotes (") and escaping chars.
So the question is, why is this response formatted like this? And how to tell the deserializer how to work with it? I'm sure that removing or replacing chars isn't the way to go.
Here is what i'm doing:
public class Value
{
public string code { get; set; }
public string description { get; set; }
public bool isSet { get; set; }
}
public class RootObject
{
public Value value { get; set; }
public int nr { get; set; }
}
var json = JsonConvert.DeserializeObject<List<RootObject>>(serviceResult);
The above doesn't work.
For the time being I have solved the issue this way. But I keep thinking that the above, with the deserializer, is more elegant.
JArray jArray = JArray.Parse(serviceResult);
List<Value> values = jArray.Select(x => JObject.Parse(x["value"].ToString()).ToObject<Value>()).ToList();
The easiest way to do this would be to use a custom JsonConverter, for example something like this:
public class StringToObjectConverter<T> : Newtonsoft.Json.JsonConverter
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
//This will only be needed if you also need to serlialise
writer.WriteRaw(JsonConvert.SerializeObject(value));
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject<T>(reader.Value.ToString());
}
public override bool CanRead => true;
//We can only work with the type T, you could expand this to cope with derived types
public override bool CanConvert(Type objectType) => typeof(T) == objectType;
}
Now using these models, noting in particular the attribute on the Value property:
public class RootObject
{
[JsonConverter(typeof(StringToObjectConverter<Value>))]
public Value value { get; set; }
public int nr { get; set; }
}
public class Value
{
public string code { get; set; }
public string description { get; set; }
public bool isSet { get; set; }
}
Now it's a simple deserialisation:
var json = "....";
var rootObjects = JsonConvert.DeserializeObject<List<RootObject>>(json);
I want to deserialize some strange JSON to C# classes:
{
"Result": {
"Client": {
"ProductList": {
"Product": [
{
"Name": {
"Name": "Car polish"
}
}
]
},
"Name": {
"Name": "Mr. Clouseau"
},
"AddressLine1": {
"AddressLine1": "Hightstreet 13"
}
}
}
}
json2csharp generates the following classes for the JSON:
public class Name
{
public string Name { get; set; }
}
public class Product
{
public Name Name { get; set; }
}
public class ProductList
{
public List<Product> Product { get; set; }
}
public class Name2
{
public string Name { get; set; }
}
public class AddressLine1
{
public string AddressLine1 { get; set; }
}
public class Client
{
public ProductList ProductList { get; set; }
public Name2 Name { get; set; }
public AddressLine1 AddressLine1 { get; set; }
}
public class Result
{
public Client Client { get; set; }
}
public class RootObject
{
public Result Result { get; set; }
}
The problem is that the duplicated property names in the objects (Name in Product and Client, AddressLine1 in Client) forces me to create an extra class with only one string property (Name, AddressLine1) to be able to deserialize the JSON.
The generated code is also invalid, because member names cannot be the same as their enclosing type (but I know that can be solved using the [JsonProperty(PropertyName = "Name")] attribute).
What's the best way to avoid that unnecessary level in the class hierarchy and have a clean class structure to be able to deserialize this JSON using JSON.NET? Note this is a third-party API, so I can't just change the JSON.
Indeed, this is a strange format for an API result, making it more difficult to consume. One idea to solve the problem is to create a custom JsonConverter that can take a wrapped value and return the inner value as if the wrapper were not there. This would allow you to deserialize the clunky JSON into a more sensible class hierarchy.
Here is a converter that should work:
class WrappedObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
// Get the value of the first property of the inner object
// and deserialize it to the requisite object type
return token.Children<JProperty>().First().Value.ToObject(objectType);
}
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 create a class hierarchy that eliminates the extra levels of nesting. You must mark the properties that need to be "unwrapped" with a [JsonConverter] attribute so that Json.Net knows when to apply the custom converter. Here is the improved class structure:
public class RootObject
{
public Result Result { get; set; }
}
public class Result
{
public Client Client { get; set; }
}
public class Client
{
[JsonConverter(typeof(WrappedObjectConverter))]
public List<Product> ProductList { get; set; }
[JsonConverter(typeof(WrappedObjectConverter))]
public string Name { get; set; }
[JsonConverter(typeof(WrappedObjectConverter))]
public string AddressLine1 { get; set; }
}
public class Product
{
[JsonConverter(typeof(WrappedObjectConverter))]
public string Name { get; set; }
}
(Note that if the Result object will not contain any other properties besides Client, you can apply the WrappedObjectConverter there as well to move the Client up to the RootObject and eliminate the Result class.)
Here is a demo showing the converter in action:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""Result"": {
""Client"": {
""ProductList"": {
""Product"": [
{
""Name"": {
""Name"": ""Car polish""
}
}
]
},
""Name"": {
""Name"": ""Mr. Clouseau""
},
""AddressLine1"": {
""AddressLine1"": ""Hightstreet 13""
}
}
}
}";
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
Client client = obj.Result.Client;
foreach (Product product in client.ProductList)
{
Console.WriteLine(product.Name);
}
Console.WriteLine(client.Name);
Console.WriteLine(client.AddressLine1);
}
}
Output:
Car polish
Mr. Clouseau
Hightstreet 13
It sounds like you may be interesting in implementing a custom JsonConverter. Here's a site that has some samples of how you could do this. It's a fairly simple process and would allow you to keep the JSON you're stuck with while having whatever class structure you're most comfortable with.
I have the following json string:
{"Visits":[true,"DockedOnly","leftZone","0","500",0,0,0],
"Weather":[true,"DockedOnly","leftZone","0","0",0,0,1],
"ContactUs":[true,"DockedOnly","leftZone","0","317",0,0,2],
"Birthdays":[true,"DockedOnly","middleZone","0","0",0,0,0],
"Reminders":[true,"DockedOnly","middleZone","0","145",0,0,1],
"Messages":[true,"DockedOnly","middleZone","0","0",0,0,2],
"Availability":[true,"DockedOnly","middleZone","0","0",0,0,3],
"Settings":[false,"DockedOnly","leftzone","0","155",0,0,0]}
Is there anyway to deserialize to something like the following?
[Serializable]
public class WidgetProps
{
public bool Visible { get; set; }
public string DockState { get; set; }
public string Zone { get; set; }
public string Top { get; set; }
public string Left { get; set; }
public int UnusedA { get; set; }
public int UnusedB { get; set; }
public int Position { get; set; }
}
[Serializable]
public class WidgetLayout
{
public WidgetProps Visits { get; set; }
public WidgetProps Weather { get; set; }
public WidgetProps ContactUs { get; set; }
public WidgetProps Birthdays { get; set; }
public WidgetProps Reminders { get; set; }
public WidgetProps Messages { get; set; }
public WidgetProps Availability { get; set; }
public WidgetProps Settings { get; set; }
}
or
public class Widget
{
public string WidgetName { get; set; }
public WidgetProps props { get; set; }
}
List<Widget> MyWidgets;
I am given the json string so I can't change how it is given to me but maybe I could tinker with it after I get it so it will work.
I tried:
string s = "{\"Visits\":[true,\"DockedOnly\",\"leftZone\",\"0\",\"500\",0,0,0],\"Weather\":[true,\"DockedOnly\",\"leftZone\",\"0\",\"0\",0,0,1],\"ContactUs\":[true,\"DockedOnly\",\"leftZone\",\"0\",\"317\",0,0,2],\"Birthdays\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"0\",0,0,0],\"Reminders\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"145\",0,0,1],\"Messages\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"0\",0,0,2],\"Availability\":[true,\"DockedOnly\",\"middleZone\",\"0\",\"0\",0,0,3],\"Settings\":[false,\"DockedOnly\",\"leftzone\",\"0\",\"155\",0,0,0]}}";
var sd = new JavaScriptSerializer().Deserialize < List<Widget>>(s);
and
var sd = new JavaScriptSerializer().Deserialize < WidgetLayout >(s);
This isn't working because you're trying to deserialize an array into an object. The json.NET deserializer will not be able make that conversion.
Because your json arrays have multiple types in them you'll have to deserialize into the smallest common denominator, in this case, object. From there I would recommend writing a method to assign each index to it's corresponding property in WidgetProps.
So basically, define this constructor;
public WidgetProps(object[] props)
{
Visible = (bool)props[0];
DockState = (string)props[1];
// ext
}
I would have something like a WidgetDirty class that I do the initial deserilization into. From there you can create a new instance of WidgetLayout by instantiating each of it's properties like myWidgetLayoutInstance.Visits = new WidgetProp(myWidgetDirtyInstance.Visits); I'd probably hide this mess in a WidgetLayout constructor that takes a WidgetDirty as it's only arg.
yes it's disgusting... but I don't know of any real alternatives because that json's design just isn't very compatible with the C# language. If you're strongly apposed to this I might look at the dynamic type. I haven't used it in C# and probably never will, but I know that deserializing that in a dynamic language like PHP would be no trouble at all.
This doesn't work, because array is normally not deserialized as an object. If possible, I think you should fix the JSON. If you can't do that, and you're using JSON.NET, you can create JsonConverter for WidgetProps that manually converts the array to the object:
class WidgetPropsConverter : JsonConverter
{
public override void WriteJson(
JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(
JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var array = serializer.Deserialize<object[]>(reader);
return new WidgetProps
{
Visible = (bool)array[0],
DockState = (string)array[1],
Zone = (string)array[2],
Top = (string)array[3],
Left = (string)array[4],
Position = (int)(long)array[7]
};
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(WidgetProps);
}
}
You would then use it like this:
var result = JsonConvert.DeserializeObject<WidgetLayout>(
jsonString, new WidgetPropsConverter());