C# JSON Newtonsoft.JSon to get name - c#

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);

Related

How to write a WebService that will accept Json with "Multi Type Property"?

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.

Deserialize partial JSON

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

Ignore serialization for one type in a list

I am using JsonSerializer to serialize/deserialize a class and it is working well.
But in this class, there is a list that I want to serialize but not for every elements in the list.
There is 3 type in this list with inheritance:
FileInformation and FolderInformation that both inherit from TreeViewElement.
How can I filter depending on the type? I want to serialize all FolderInformations Instance but not FileInformations.
You can use a JsonConverter attribute on your list property to filter the list during serialization.
Here's an example I wrote in LINQPad:
void Main()
{
var document = new Document
{
Id = 123,
Properties = {
new Property { Name = "Filename", Value = "Mydocument.txt" },
new Property { Name = "Length", Value = "1024" },
new Property {
Name = "My secret property",
Value = "<insert world domination plans here>",
IsSerializable = false
},
}
};
var json = JsonConvert.SerializeObject(document, Formatting.Indented).Dump();
var document2 = JsonConvert.DeserializeObject<Document>(json).Dump();
}
public class Document
{
public int Id { get; set; }
[JsonConverterAttribute(typeof(PropertyListConverter))]
public List<Property> Properties { get; } = new List<Property>();
}
public class Property
{
[JsonIgnore]
public bool IsSerializable { get; set; } = true;
public string Name { get; set; }
public string Value { get; set; }
}
public class PropertyListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<Property>);
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var list = (existingValue as List<Property>) ?? new List<Property>();
list.AddRange(serializer.Deserialize<List<Property>>(reader));
return list;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
var list = (List<Property>)value;
var filtered = list.Where(p => p.IsSerializable).ToList();
serializer.Serialize(writer, filtered);
}
}
The output:
{
"Id": 123,
"Properties": [
{
"Name": "Filename",
"Value": "Mydocument.txt"
},
{
"Name": "Length",
"Value": "1024"
}
]
}
You would have to adapt your attribute to your own types and filtering criteria but this should get you started.
You can use the [JsonIgnore] Attribute, to ignore the whole property
Like this:
class Cls
{
public string prop1 { get; set; }
public string prop2 { get; set; }
[JsonIgnore]
public string prop3 { get; set; }
}
class Program
{
static void Main(string[] args)
{
var cls = new Cls
{
prop1 = "lorem",
prop2 = "ipsum",
prop3 = "dolor"
};
System.Console.WriteLine(JsonConvert.SerializeObject(cls));
//Output: {"prop1":"lorem","prop2":"ipsum"}
}
}
EDIT: For ignoring only the value.
If its only the value, needs to be ignored and not the whole property:
class Cls
{
public List<string> prop1 { get; set; }
public List<string> prop2 { get; set; }
public List<string> prop3 { get; set; }
[OnSerializing()]
internal void OnSerializingMethod(StreamingContext context)
{
prop3 = null;
}
}
class Program
{
static void Main(string[] args)
{
var cls = new Cls
{
prop1 = new List<string>{ "lorem", "ipsum", "dolor" },
prop2 = new List<string>{ "lorem", "ipsum", "dolor" },
prop3 = new List<string>{ "lorem", "ipsum", "dolor" },
};
System.Console.WriteLine(JsonConvert.SerializeObject(cls)); //Output: {"prop1":["lorem"],"prop2":["lorem"],"prop3":null}
}
}

How to parse this JSON using Newton Soft for nested object

How to parse this JSON
using Newton Soft
I tried but gives me null as my modal should have class 1, 2, 3 ...
but that's dynamic .So getting confuse.
Appreciate your help !
{
"data": {
"1": {
"test": {
"col1": "123",
"col2": "name"
}
},
"2": {
"test": {
"col1": "345",
"col2": "name2"
}
},
"3": {
"test": {
"col1": "456",
"col2": "name3"
}
}
}
class root
{
data data{get; set;};
}
class data
{
List<object> innerObject {get; set;} //not sure as labels are dynamic
}
class test
{
col1{get; set;}
col2{get; set;}
} //Calling it in that way ..
root r = JsonConvert.DeserializeObject<root>(result);
You can use a dictionary to parse a JSON object with custom property names:
public class Test
{
public string col1 { get; set; }
public string col2 { get; set; }
}
public class DataValue
{
public Test test { get; set; }
}
public class RootObject
{
public RootObject() { this.data = new Dictionary<string, DataValue>(); }
public Dictionary<string, DataValue> data { get; set; }
}
See Deserialize a Dictionary.
If you are sure the dictionary keys will always be numeric, you can use integer keys, and use a SortedDictionay to order the values:
public class RootObject
{
public RootObject() { this.data = new SortedDictionary<long, DataValue>(); }
public SortedDictionary<long, DataValue> data { get; set; }
}
#dbc's answer is a good option, but you have a few other options as well:
If you have control over the JSON that's being generated, make your life easier by using an Array if you can. It looks like what you would really want is this:
{
"data": [{
"test": {
"col1": "123",
"col2": "name"
}, {
"test": {
"col1": "345",
"col2": "name2"
}, /* etc */
]
}
This way, data represents an array and you can deserialize it as such:
class Root
{
List<Data> Data { get; set; };
}
class Data
{
Test Test { get; set; }
}
JsonConvert.DeserializeObject<Root>(json);
You could force your JSON into an array structure using a custom converter. This is making a lot of assumptions about your JSON. For example, it's assuming that you have an object with a integer keys, with no spaces in between numbers:
public class ObjectAsArrayConverter : JsonConverter
{
public override bool CanConvert(Type type)
{
return
type.IsGenericType &&
typeof(List<>) == type.GetGenericTypeDefinition() &&
typeof(Data) == type.GetGenericArguments()[0];
}
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
JObject obj = JObject.ReadFrom(reader).ToObject<JObject>();
return obj.Properties()
.Select(p => new { Index = int.Parse(p.Name), Value = obj[p.Name].ToObject<Data>() })
.OrderBy(p => p.Index)
.Select(p => p.Value)
.ToList();
}
public override void WriteJson(
JsonWriter writer,
object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This would allow you to deserialize your JSON into this structure:
class Root
{
public List<Data> Data {get; set;}
}
class Data
{
public Test Test { get; set; }
}
class Test
{
public string Col1 {get; set;}
public string Col2 {get; set;}
}
Example: https://dotnetfiddle.net/e2Df7h
You might need to tweak the second suggestion, for example if you expect the array to be sparse you might want to make the code expect that.

C# Parsing JSON Issue

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);

Categories