I'm trying to deserialize some partial JSON received from an old SignalR service.
This is an example of the original JSON:
{
"opt": {
"data": {
"DR": [{
"O": [
null,
"18:46.401",
"RGGW.GWWWR",
"4.1",
19,
"17.852",
"42.455",
"",
null,
"+3.893",
"277",
"306",
"",
"310",
"+0.058",
null
],
"OC": [
"1"
]
},
{
"O": [
null,
"1:41.119",
"GYYG.WWWWW",
"1.0",
2,
"17.561",
"43.485",
"40.073",
null,
"+16.772",
"275",
"291",
"218",
"291",
"+16.772",
null
],
"OC": [
"1"
]
}
]
}
}
}
and this the partial JSON received:
{
"opt": {
"data": {
"DR": {
"1": {
"O": {
"2": "WYYW.WWWWW",
"7": "42.283",
"12": "212"
}
}
}
}
}
}
and these are my classes:
public class DR2
{
[JsonProperty("O")]
public List<object> O { get; set; }
[JsonProperty("OC")]
public List<string> OC { get; set; }
}
public class Data4
{
[JsonProperty("DR")]
public List<DR2> DR { get; set; }
}
public class Opt
{
[JsonProperty("data")]
public Data4 data { get; set; }
}
public class SPFeed
{
[JsonProperty("opt")]
public Opt opt { get; set; }
}
Trying to deserialize I receive the classic error:
JsonSerializationException: Cannot deserialize the current JSON object ... because the type requires a JSON array.
SPFeed partial_opt = JsonConvert.DeserializeObject<SPFeed>(test); // The error above
Also, trying to merge the 2 JSON with the code below, no merge made, but only substitution:
var object1 = JObject.Parse(JsonConvert.SerializeObject(spfeed));
var object2 = JObject.Parse(JsonConvert.SerializeObject(partial_opt));
object1.Merge(object2, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Merge
});
I think because the partial JSON in not a correct array.
Any other method to deserialize?
Resolved:
var obj = JObject.Parse(partialJson);
Opt opt = root.SPFeed.opt;
if (obj["opt"]["data"]["DR"] != null) {
IList<JToken> DR = JObject.Parse(obj["opt"]["data"]["DR"].ToString());
var DRindex = Convert.ToInt32(((JProperty)DR[0]).Name);
var O = ((JProperty)DR[0]).Value;
JToken Otemp = JToken.Parse(O.ToString());
if (Otemp["O"] != null)
{
var Oindex = Otemp["O"];
foreach (JToken item in Oindex)
{
int index = Convert.ToInt32(((JProperty)item).Name);
string value = ((JProperty)item).Value.ToString();
Console.WriteLine("Index {0} Value {1}", index, value);
opt.data.DR[DRindex].O[index] = value;
}
}
};
This may not be the cleanest solution, but if you can detect if the JSON is partial or not, then you could use a Custom Contract Resolver. This would also required some changes to your class structure. Redefining structure as
public interface IData{}
public class Data4Partial:IData
{
[JsonProperty("DR")]
public Dictionary<string,Dictionary<string,Dictionary<string,string>>> Data{get;set;}
}
public class DR2Full:IData
{
[JsonProperty("O")]
public List<object> O { get; set; }
[JsonProperty("OC")]
public List<string> OC { get; set; }
}
public class Data4Full:IData
{
[JsonProperty("DR")]
public List<DR2Full> DR { get; set; }
}
public class Opt
{
[JsonProperty("data")]
public IData data { get; set; }
}
public class SPFeed
{
[JsonProperty("opt")]
public Opt opt { get; set; }
}
The Contract Resolver would aim to replace the PropertyType so that result could be adapted to Partial and Full Json. For example,
public class GenericContractResolver<T> : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.UnderlyingName == "data")
{
property.PropertyType = typeof(T);
}
return property;
}
}
You could now use it as
var partialResult = JsonConvert.DeserializeObject<SPFeed>(partialJson, new JsonSerializerSettings
{
ContractResolver = new GenericContractResolver<Data4Partial>()
});
Or
var fullResult = JsonConvert.DeserializeObject<SPFeed>(fullJson
, new JsonSerializerSettings
{
ContractResolver = new GenericContractResolver<Data4Full>()
});
Partial Json Output
Complete Json Output
Related
I have below json which I need to deserialize in C# -
{
"clientSettings":[
{
"clientId":12345,
"entries":[
{
"key":"abc",
"value":false
},
{
"key":"def",
"value":false
},
{
"key":"ghi",
"value":false
}
]
},
{
"clientId":9876,
"entries":[
{
"key":"lkmn",
"value":false
}
]
}
],
"productSettings":[
{
"productId":11,
"entries":[
{
"key":"jkl",
"value":true
},
{
"key":"mno",
"value":true
}
]
},
{
"productId":12,
"entries":[
{
"key":"jkl",
"value":true
},
{
"key":"mno",
"value":true
}
]
}
],
"customerSettings":[
{
"key":"enableData",
"value":false
},
{
"key":"minPriceValue",
"value":"10.28"
},
{
"key":"presentData",
"value":"AEGIS"
}
],
"thothTest":{
"9876":[
"K"
],
"5431":[
"A",
"L"
],
"5123":[
"L"
]
},
"osirisTest":{
"7678":[
"K"
]
}
}
Below is the classes I created to deserialzie json into -
public class ProcessHolder : Holder
{
public IDictionary<int, ISet<string>> OsirisTest { get; set; }
public IDictionary<int, ISet<string>> ThothTest { get; set; }
}
public class Holder
{
public IList<Mapping> CustomerSettings { get; set; }
public IList<ClientSettingsMapping> ClientSettings { get; set; }
public IList<ProductSettingsMapping> ProductSettings { get; set; }
}
public class Mapping
{
public string Key { get; set; }
public object Value { get; set; }
}
public class ProductSettingsMapping : Mapping
{
public int ProductId { get; set; }
}
public class ClientSettingsMapping : Mapping
{
public int ClientId { get; set; }
}
I want to load all customerSettings values into CustomerSettings object of Holder class.
Similarly I want to load all clientSettings values into ClientSettings object of Holder class.
Similarly all productSettings values into ProductSettings object of Holder class.
Similarly thothTest values into ThothTest and osirisTest values into OsirisTest object.
I was trying with below code but somehow I am not able to see Key and Value variables being populated inside Mapping class object after deserializing my above json.
private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
public static void Main(string[] args)
{
var jsonContent = File.ReadAllText("/beta/Downloads/test.json");
var config = JsonConvert.DeserializeObject<ProcessHolder>(jsonContent, serializerSettings);
if (config == null)
{
Console.WriteLine("Some Parsing Issue");
}
// using config object here
}
What is wrong I am doing here? I just need to deserialize my json into above classes and give me ProcessHolder object back which I can use later on.
Looking at your JSON, clientSettings and productSettings have entries array. There is no corresponding matching property in your class definition for same.
I would suggest to modify your class definition as per JSON. Try below and see if it helps:
public class ProductSettingsMapping
{
public int ProductId { get; set; }
public IList<Mapping> Entries { get; set; }
}
public class ClientSettingsMapping
{
public int ClientId { get; set; }
public IList<Mapping> Entries { get; set; }
}
Second option - if you are not able to modify classes, you can amend JSON. I am providing sample for clientSettings
{
"clientSettings": [
{
"clientId": 12345,
"key": "abc",
"value": false
},
{
"clientId": 12345,
"key": "def",
"value": false
},
{
"clientId": 12345,
"key": "ghi",
"value": false
},
{
"clientId": 9876,
"key": "lkmn",
"value": false
}
]
}
You can update productSettings accordingly.
Third option is to parse into JObject and then convert to your classes.
A sample for clientSettings
static void Main(string[] args)
{
var json = "{'clientSettings':[{'clientId':12345,'entries':[{'key':'abc','value':false},{'key':'def','value':false},{'key':'ghi','value':false}]},{'clientId':9876,'entries':[{'key':'lkmn','value':false}]}],'productSettings':[{'productId':11,'entries':[{'key':'jkl','value':true},{'key':'mno','value':true}]},{'productId':12,'entries':[{'key':'jkl','value':true},{'key':'mno','value':true}]}],'customerSettings':[{'key':'enableData','value':false},{'key':'minPriceValue','value':'10.28'},{'key':'presentData','value':'AEGIS'}],'thothTest':{'9876':['K'],'5431':['A','L'],'5123':['L']},'osirisTest':{'7678':['K']}}";
var parsed = JObject.Parse(json);
var parsedClientSettings = parsed["clientSettings"];
List<ClientSettingsMapping> clientSettings = new List<ClientSettingsMapping>();
foreach (var parsedClientSetting in parsedClientSettings)
{
var clientId = parsedClientSetting.Value<int>("clientId");
foreach (var entry in parsedClientSetting["entries"])
{
clientSettings.Add(new ClientSettingsMapping { ClientId = clientId, Key = entry["key"].ToString(), Value = entry["value"].ToString() });
}
}
ProcessHolder processHolder = new ProcessHolder() { ClientSettings = clientSettings };
Console.ReadLine();
}
You should define "entries" in both ProductSettingsMapping and ClientSettingsMapping class.
public class ProductSettingsMapping
{
public int ProductId { get; set; }
public IList<Mapping> entries { get; set; }
}
public class ClientSettingsMapping
{
public int ClientId { get; set; }
public IList<Mapping> entries { get; set; }
}
Context:
Cust has a service that send the following json. He can easly change the target of that query but not the query it self.
I have to build a WebService that accept query like the following JSON.
While I will have no issue handeling the Json, I have an issue trying to define the method/interface that will accept a query like this.
The issue comes from Houses > Things: It's a dictionary of string, "objectThing" where "objectThing" has a property value that may hold multiple type.
EG:
int , "Value": 42
string , "Value": "Catty"
string array, "Value": ["Book1", "Book2", "Book3"]
object , A limited List of Object Type
"Value":
{
"PeopleId": "1234ABCD",
"Name": "John"
}
object array, An Array of with limited List of Object Type
"Value": [
{
"PeopleId": "1234ABCD",
"Name": "John"
},
{
"PeopleId": "0000AAAA",
"Name": "Doe"
}
]
Value is not Dynamic for me. It's within a limited list of Type that I can define.
Json Example:
{
"RootID" : "0123456",
"FooID" : "0123456",
"BarID" : "0123456",
"Houses" :[
{
"OwnerId" : "0123456",
"Date" : 1890895600000,
"Location" : {
"Latitude" : -1,
"Longitude" : -1
},
"Things" :{
"1" :{
"Label": "Books",
"Type" : "List",
"Value": ["Book1", "Book2", "Book3"]
},
"2" :{
"Label": "Cat",
"Type" : "Text",
"Value": "Catty"
},
"3" :{
"Label": "A Number",
"Type" : "Int",
"Value": 42
},
"4" :{
"Label": "Peoples",
"Type" : "People",
"Value": [
{
"PeopleId": "1234ABCD",
"Name": "John"
},
{
"PeopleId": "0000AAAA",
"Name": "Doe"
}
]
}
}
},
{
"OwnerId" : "111111",
"Things" :{}
},
{
"OwnerId" : "000001",
"Things" :{}
}
]
}
And the Class definition, If I were to deserialize this Json into a proper type:
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class QueryRoot
{
[JsonProperty("RootID")]
public string RootId { get; set; }
[JsonProperty("FooID")]
public string FooId { get; set; }
[JsonProperty("BarID")]
public string BarId { get; set; }
[JsonProperty("Houses")]
public List<House> Houses { get; set; }
}
public partial class House
{
[JsonProperty("OwnerId")]
public string OwnerId { get; set; }
[JsonProperty("Date", NullValueHandling = NullValueHandling.Ignore)]
public long? Date { get; set; }
[JsonProperty("Location", NullValueHandling = NullValueHandling.Ignore)]
public Location Location { get; set; }
[JsonProperty("Things")]
public Dictionary<string, Thing> Things { get; set; }
}
public partial class Location
{
[JsonProperty("Latitude")]
public long Latitude { get; set; }
[JsonProperty("Longitude")]
public long Longitude { get; set; }
}
public partial class Thing
{
[JsonProperty("Label")]
public string Label { get; set; }
[JsonProperty("Type")]
public string Type { get; set; }
[JsonProperty("Value")]
public ThingValue Value { get; set; }
}
public partial class ValueClass
{
[JsonProperty("PeopleId")]
public string PeopleId { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
}
public partial struct ValueElement
{
public string String;
public ValueClass ValueClass;
public static implicit operator ValueElement(string String) => new ValueElement { String = String };
public static implicit operator ValueElement(ValueClass ValueClass) => new ValueElement { ValueClass = ValueClass };
}
public partial struct ThingValue
{
public List<ValueElement> AnythingArray;
public long? Integer;
public string String;
public static implicit operator ThingValue(List<ValueElement> AnythingArray) => new ThingValue { AnythingArray = AnythingArray };
public static implicit operator ThingValue(long Integer) => new ThingValue { Integer = Integer };
public static implicit operator ThingValue(string String) => new ThingValue { String = String };
}
public partial class QueryRoot
{
public static QueryRoot FromJson(string json) => JsonConvert.DeserializeObject<QueryRoot>(json, QuickType.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this QueryRoot self) => JsonConvert.SerializeObject(self, QuickType.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
ThingValueConverter.Singleton,
ValueElementConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class ThingValueConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(ThingValue) || t == typeof(ThingValue?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.Integer:
var integerValue = serializer.Deserialize<long>(reader);
return new ThingValue { Integer = integerValue };
case JsonToken.String:
case JsonToken.Date:
var stringValue = serializer.Deserialize<string>(reader);
return new ThingValue { String = stringValue };
case JsonToken.StartArray:
var arrayValue = serializer.Deserialize<List<ValueElement>>(reader);
return new ThingValue { AnythingArray = arrayValue };
}
throw new Exception("Cannot unmarshal type ThingValue");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (ThingValue)untypedValue;
if (value.Integer != null)
{
serializer.Serialize(writer, value.Integer.Value);
return;
}
if (value.String != null)
{
serializer.Serialize(writer, value.String);
return;
}
if (value.AnythingArray != null)
{
serializer.Serialize(writer, value.AnythingArray);
return;
}
throw new Exception("Cannot marshal type ThingValue");
}
public static readonly ThingValueConverter Singleton = new ThingValueConverter();
}
internal class ValueElementConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(ValueElement) || t == typeof(ValueElement?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
case JsonToken.Date:
var stringValue = serializer.Deserialize<string>(reader);
return new ValueElement { String = stringValue };
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<ValueClass>(reader);
return new ValueElement { ValueClass = objectValue };
}
throw new Exception("Cannot unmarshal type ValueElement");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (ValueElement)untypedValue;
if (value.String != null)
{
serializer.Serialize(writer, value.String);
return;
}
if (value.ValueClass != null)
{
serializer.Serialize(writer, value.ValueClass);
return;
}
throw new Exception("Cannot marshal type ValueElement");
}
public static readonly ValueElementConverter Singleton = new ValueElementConverter();
}
I already have a WCF Service that handles Json. It work fine the issue is declaring the method/interface that will accept this kind of query.
If WCF Web Service is a limiting factor, or if (ASP.NET/Core) Web APIs provides an easier path it's welcom.
You can receive a JSON string and convert it to an object. Here is a demo:
[WebMethod]
public string HelloWorld()
{
Stream s = HttpContext.Current.Request.InputStream;
byte[] b = new byte[s.Length];
s.Read(b, 0, (int)s.Length);
string jsontext = Encoding.UTF8.GetString(b);
var productProperty = JsonHelper.JsonDeserialize<School>(jsontext); //Deserialize JSON strings to objects
return "Hello World";
}
This is the method in WebService.
[DataContract]
public class School
{
[DataMember]
public int ClassroomId { set; get; }
[DataMember]
public List<Student> StudentList { set; get; }
}
[DataContract]
public class Student
{
[DataMember]
public int StudentId { set; get; }
[DataMember]
public string StudentName { set; get; }
}
This is the object to be converted by JSON string.
public class JsonHelper
{
public static string JsonSerializer<T>(T t)
{
var ser = new DataContractJsonSerializer(typeof(T));
var ms = new MemoryStream();
ser.WriteObject(ms, t);
string jsonString = Encoding.UTF8.GetString(ms.ToArray());
ms.Close();
return jsonString;
}
public static T JsonDeserialize<T>(string jsonString)
{
var ser = new DataContractJsonSerializer(typeof(T));
var ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
var obj = (T)ser.ReadObject(ms);
return obj;
}
}
To deserialize JSON characters into objects,there are many open-source class libraries. I use the DatacontractJsonSerializer that comes with .net version 3.5 or above.I wrote a JsonHelper class.
The JSON looks like this
{
"123": {
"Type": "IN",
"OUTAgentMACID": "00-14-22-01-23-45",
"PlateNumber": {
"Image": "/poll/data/date0/img.png",
"Number": "ABC1234",
"TimeStamp": 5901291
}
},
"124": {
"Type": "OUT",
"OUTAgentMACID": "00-14-22-01-31-45",
"PlateNumber": {
"Image": "/poll/data/date0/img.png",
"Number": "ABC1234",
"TimeStamp": 5991291
}
},
"125": {
"Type": "IN",
"INAgentMACID": "00-14-22-01-63-45",
"PlateNumber": {
"Image": "/poll/data/date1/img.png",
"Number": "ABC1234",
"TimeStamp": 6001239
}
}
}
The probable class structure is
public class PlateNumber
{
public string Image { get; set; }
public string Number { get; set; }
public int TimeStamp { get; set; }
}
public class Activity
{
public string Type { get; set; }
public string AgentMACID { get; set; }
public PlateNumber PlateNumber { get; set; }
}
public class SessionActivity
{
public Dictionary<int, Activity> Activities { get; set; }
}
Helper looks like this
public class helpers : DefaultContractResolver
{
private Dictionary<string, string> PropertyMappings { get; set; }
public helpers()
{
PropertyMappings = new Dictionary<string, string>
{
{"INAgentMACID", "AgentMACID"},
{"OUTAgentMACID", "AgentMACID"},
};
}
protected override string ResolvePropertyName(string propertyName)
{
string resolvedName = null;
var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
Now when I try to deserializing it like this
var settings = new JsonSerializerSettings();
settings.ContractResolver = new helpers();
var activities = JsonConvert.DeserializeObject<SessionActivity>("Some.json"), settings);
the activities is null.
The Problem is AgentMACID Since the JSON have either OUTAgentMACID or INAgentMACID depending on the Type
Please help me design the class for this JSON.
I think it's not directly possible that you have one Property, which represents two Properties of the json- (as I've read here Make JsonPropertyAttribute allow multiple usages on same property)
What I undertsand from this post, you would be forced to have another property, which just "forwards" the value to the one you want.
example:
public class Activity
{
public string Type { get; set; }
public string AgentMACID { get; set; }
private string AgentMACID2 { set { AgentMACID = value; } } // used to map the other field of json
public PlateNumber PlateNumber { get; set; }
}
in the Contract Resolver you have to mape vias versa as you did. With the second field I added, it might looks as this:
PropertyMappings = new Dictionary<string, string>
{
{"AgentMACID","OUTAgentMACID"},
{"AgentMACID2","INAgentMACID"}
};
And deserialize by this:
var activities = JsonConvert.DeserializeObject<Dictionary<int, Activity>>("json content", settings);
1 - Deserialize a Dictionary from JSON
Based on your json object :
{
"123": {
"Type": "IN",
"OUTAgentMACID": "00-14-22-01-23-45",
"PlateNumber": {
"Image": "/poll/data/date0/img.png",
"Number": "ABC1234",
"TimeStamp": 5901291
}
},
"124": {
"Type": "OUT",
"OUTAgentMACID": "00-14-22-01-31-45",
"PlateNumber": {
"Image": "/poll/data/date0/img.png",
"Number": "ABC1234",
"TimeStamp": 5991291
}
},
"125": {
"Type": "IN",
"INAgentMACID": "00-14-22-01-63-45",
"PlateNumber": {
"Image": "/poll/data/date1/img.png",
"Number": "ABC1234",
"TimeStamp": 6001239
}
}
}
You can deserialize a dictionary using :
var activities = JsonConvert.DeserializeObject<Dictionary<int, Activity>>("Some.json"), settings);
2.1 - Manage multiple json property names in one C# property
For your second issue you can define your Activity class like this :
public class Activity
{
public string Type { get; set; }
public string AgentMACID { get; set; }
// Optional: you can rename your property with this property attribute
// [JsonProperty("INAgentMACID")]
public string INAgentMACID { set { AgentMACID = value; } }
// Optional: you can rename your property with this property attribute
// [JsonProperty("OUTAgentMACID")]
public string OUTAgentMACID { set { AgentMACID = value; } }
public PlateNumber PlateNumber { get; set; }
}
2.2 - You can also inherit DefaultContractResolver as you were doing :
public class RenamePropertySerializerContractResolver : DefaultContractResolver
{
private readonly Dictionary<Type, HashSet<string>> _ignores;
private readonly Dictionary<Type, Dictionary<string, string>> _renames;
public RenamePropertySerializerContractResolver()
{
_renames = new Dictionary<Type, Dictionary<string, string>>();
}
public void RenameProperty(Type type, string propertyName, string newJsonPropertyName)
{
if (!_renames.ContainsKey(type))
_renames[type] = new Dictionary<string, string>();
_renames[type][propertyName] = newJsonPropertyName;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (IsRenamed(property.DeclaringType, property.PropertyName, out var newJsonPropertyName))
property.PropertyName = newJsonPropertyName;
return property;
}
private bool IsRenamed(Type type, string jsonPropertyName, out string newJsonPropertyName)
{
Dictionary<string, string> renames;
if (!_renames.TryGetValue(type, out renames) || !renames.TryGetValue(jsonPropertyName, out newJsonPropertyName))
{
newJsonPropertyName = null;
return false;
}
return true;
}
}
And finally use you new ContractResolver :
var jsonResolver = new RenamePropertySerializerContractResolver();
jsonResolver.RenameProperty(typeof(Activity), "INAgentMACID", "AgentMACID");
jsonResolver.RenameProperty(typeof(Activity), "OUTAgentMACID", "AgentMACID");
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = jsonResolver;
var activities = JsonConvert.DeserializeObject<IDictionary<int, Activity>>(json, serializerSettings);
sources : https://blog.rsuter.com/advanced-newtonsoft-json-dynamically-rename-or-ignore-properties-without-changing-the-serialized-class/
I have following JSon and I am using Json.NET (Newtonsoft.Json):
{
"total_items": "62",
"page_number": "6",
"page_size": "10",
"page_count": "7",
"cars": {
"car": [
{
"car_name": "Honda",
"engines": {
"engine": [
{
"name": "1.2L"
},
{
"name": "1.8L"
}
]
},
"country": "Japan"
},
{
"car_name": "Ford",
"engines": {
"engine": {
"name": "2.2L"
}
},
"country": "Japan"
},
{
"car_name": "VW",
"engines": null,
"country": "Germany"
}
]
}
}
And I have following Car object:
class Car
{
public Car() { }
public string Name { get; set; }
public string Country { get; set; }
public List<String> EngineNames { get; set; }
}
I need to handle case if "engines=null". If it is not null, then get all engine names. So, for example above, my EngineNames list for Honda and VW would be:
Honda.EngineNames = {"1.2L", "1.8L"} // has 2 names
VW.EngineNames = null //has nothing since no engine is provided
I need to pars the JSON above to get car data. I am parsing car_name and country but I dont know how to parse all engine names in array of engines (array could be null).
private Cars GetCars(string json)
{
dynamic data = (JObject)JsonConvert.DeserializeObject(json);
foreach (dynamic d in data.cars.car)
{
Car c = new Car();
c.Name = (string)d.SelectToken("car_name");
c.Country = (string)d.SelectToken("country");
c.EngineNames = //HOW TO GET ALL ENGINE NAMES AND HANDLE NULL ?
CarList.Add(c);
}
return CarList;
}
The best approach is to cut this dynamic a-la-javascript kind of crap out and just define your strongly typed models that will match your JSON structure:
public class Wrapper
{
public Cars Cars { get; set; }
}
public class Cars
{
public Car[] Car { get; set; }
}
public class Car
{
[JsonProperty(PropertyName = "car_name")]
public string Name { get; set; }
public string Country { get; set; }
public Engines Engines { get; set; }
}
public class Engines
{
public Engines()
{
Engine = new Engine[0];
}
// We need to use a custom JSON converter
// because of this pretty broken schema that you have
// in which the engine property can be array and a standard
// object at the same time
[JsonConverter(typeof(EnginesConverter))]
public Engine[] Engine { get; set; }
}
public class Engine
{
public string Name { get; set; }
}
and then just let JSON.NET do the magic of converting this string back into your strongly typed objects:
var wrapper = JsonConvert.DeserializeObject<Wrapper>(json);
and now that you have a strongly typed structure, you could more than easily map this to a desired C# DTO using LINQ:
public class CarsDto
{
public CarsDto()
{
Engines = new List<string>();
}
public string Name { get; set; }
public string Country { get; set; }
public List<string> Engines { get; set; }
}
and then:
var dto = wrapper.Cars.Car.Select(c => new CarsDto
{
Name = c.Name,
Country = c.Country,
Engines = (c.Engines ?? new Engines()).Engine.Select(e => e.Name).ToList(),
}).ToList();
and finally here's the custom JSON converter that we used:
public class EnginesConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<Engine[]>(reader);
}
else
{
Engine e = serializer.Deserialize<Engine>(reader);
return new[] { e };
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Why not continue using dynamic typing, and access object properties as dynamics, so you can do:
var car = new Car();
car.Name = (string)d.car_name;
car.EngineNames = (d.engines != null ? ((IEnumerable)d.engines).Cast<dynamic>().Select(e => (string)e.name) : null);
I have following JSON and I am using Json.NET (Newtonsoft.Json):
{
"total_items": "62",
"page_number": "6",
"page_size": "10",
"page_count": "7",
"cars": {
"car": [
{
"car_name": "Honda",
"engines": {
"engine": [ <-- HONDA has multiple engines, so this is an array
{
"name": "1.2L"
},
{
"name": "1.8L"
}
]
},
"country": "Japan"
"image": {
"thumb": {
"url": "http://image_path/Honda.jpg" <-- Image provided
}
}
},
{
"car_name": "Ford",
"engines": {
"engine": { <-- FORD has single engine, so this is an object
"name": "2.2L"
}
},
"country": "Japan"
"image": null <-- image is null
},
{
"car_name": "VW",
"engines": null, <-- VW has no engines, so this is null
"country": "Germany"
"image": null <-- image is null
}
]
}
}
And I have following Car object:
class Car
{
public Car() { }
public string Name { get; set; }
public string Country { get; set; }
public List<String> EngineNames { get; set; }
}
I need to handle all 3 cases above (array for HONDA, object for FORD, null for VW). If it is not null, then get all engine names. So, for example above, my EngineNames list for the 3 cars would be:
Honda.EngineNames = {"1.2L", "1.8L"} // array in JSON
Ford.EngineNames = {"2.2L"} //object in JSON
VW.EngineNames = null //null in JSON
I need to parse the JSON above to get car data. I am parsing car_name and country but I don't know how to parse all engine names by handling the 3 situations above.
private Cars GetCars(string json)
{
dynamic data = (JObject)JsonConvert.DeserializeObject(json);
foreach (dynamic d in data.cars.car)
{
Car c = new Car();
c.Name = (string)d.SelectToken("car_name");
c.Country = (string)d.SelectToken("country");
// PROBLEM: This works fine for array or null in JSON above (HONDA and VW), but it errors on JSON object (in case of FORD)
// When handling FORD, I get error "'Newtonsoft.Json.Linq.JProperty' does not contain a definition for 'name'"
c.EngineNames = (d.engines != null ? ((IEnumerable)d.engines.engine).Cast<dynamic>().Select(e => (string)e.name) : null);
CarList.Add(c);
}
return CarList;
}
Using the converter from here (originally a proposed duplicate, but this question had some other issues with the JSON)
Your class structure needs to be modified a bit.
Looking at this JSON:
"cars": { <-- cars is an object, not an array
"car": [ <-- the cars object actually contains the array
{
"car_name": "Honda",
"engines": { <-- same goes for this
"engine": [
{
Therefore, you'll need to write wrapper classes to properly reflect the JSON. Here's what I've come up with:
public class Root
{
public CarHolder Cars {get;set;}
}
public class CarHolder
{
public IList<Car> Car { get; set; }
}
public class Car
{
public Car() { }
public string car_name { get; set; }
public string Country { get; set; }
public EngineHolder Engines { get; set; }
}
public class EngineHolder
{
[JsonConverter(typeof(SingleOrArrayConverter<Engine>))]
public List<Engine> Engine { get; set; }
}
public class Engine
{
public string Name { get; set; }
}
And using the convert from the above question:
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Usage:
var result = JsonConvert.DeserializeObject<Root>(jsonStr);
Console.WriteLine(result.Cars.Car[0].Engines.Engine[0].Name == "1.2L");
Console.WriteLine(result.Cars.Car[0].Engines.Engine[1].Name == "1.8L");
Console.WriteLine(result.Cars.Car[1].Engines.Engine[0].Name == "2.2L");
Console.WriteLine(result.Cars.Car[2].Engines == null);
All print true
Looping through the cars & engines
foreach(var car in result.Cars.Car)
{
if (car.Engines != null)
{
foreach(var engine in car.Engines.Engine)
{
var engineName = engine.Name;
}
}
}
You should be able to use this as your class structure;
public class Rootobject
{
public string total_items { get; set; }
public string page_number { get; set; }
public string page_size { get; set; }
public string page_count { get; set; }
public Cars cars { get; set; }
}
public class Cars
{
public Car[] car { get; set; }
}
public class Car
{
public string car_name { get; set; }
public Engines engines { get; set; }
public string country { get; set; }
}
public class Engines
{
public object engine { get; set; }
}
//I created below class manually
public class Engine
{
public string name { get; set; }
}
I used inbuilt functionality of VS to generate this. Steps;
Open a new cs file.
Copy your json
Go to Edit menu> Paste special
Select Paste JSON as classes
Once this is done, it should be just a matter of creating two methods to serialize and deserialize.
Updated with serialise/deserialise methods
private static T Deserialise<T>(string json)
{
var myopject = JsonConvert.DeserializeObject<T>(json);
return myopject;
}
private static string Serialise<T>(T value)
{
var mycontent = JsonConvert.SerializeObject(value);
return mycontent;
}
Now to test above methods, you can do this.
var jsonstring = #"{
""total_items"": ""62"",
""page_number"": ""6"",
""page_size"": ""10"",
""page_count"": ""7"",
""cars"": {
""car"": [
{
""car_name"": ""Honda"",
""engines"": {
""engine"": [
{
""name"": ""1.2L""
},
{
""name"": ""1.8L""
}
]
},
""country"": ""Japan""
},
{
""car_name"": ""Ford"",
""engines"": {
""engine"": {
""name"": ""2.2L""
}
},
""country"": ""Japan""
},
{
""car_name"": ""VW"",
""engines"": null,
""country"": ""Germany""
}
]
}
}";
var myobject = Deserialise<Rootobject>(jsonstring);
//if you want to parse engines you can do something like this.
if (myobject.cars != null && myobject.cars.car != null && myobject.cars.car.Any())
{
foreach (Car car in myobject.cars.car)
{
if (car.engines != null && car.engines.engine != null)
{
bool isList = false;
try
{
var eng = Deserialise<Engine>(car.engines.engine.ToString());
}
catch
{
isList = true;
}
if (isList)
{
try
{
var eng = Deserialise<List<Engine>>(car.engines.engine.ToString());
}
catch
{
Debug.WriteLine("Not a list");
}
}
}
}
}
var myjson = Serialise(myobject);