Convert nested Firebase database object to C# object using Json.NET - c#

I need to convert firebase-ish data structure to C# object using JSON.Net library.
I store my data on Firebase Database like following;
{
"PoolDatas": {
"-LGGJGTAv_DPtzkmjIbl": {
"CoinType": 2,
"PoolType": 4,
"Alias": "First Alias",
"Address": "0xAE12EF212",
"Alarms": {
"-LGsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 2,
"Val": 100
},
"AlarmType": 3
},
"-LBAsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 2
}
}
},
"-LEAJGTAv_DPtzkmjIbl": {
"CoinType": 1,
"PoolType": 1,
"Alias": "Second Alias",
"Address": "0xAeeeEF212",
"Alarms": {
"-LGsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 10,
"Val": 120
},
"AlarmType": 1
},
"-LBAsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 1,
"Val": 250
},
"AlarmType": 2
}
}
}
}
}
All list objects have firebase generated ID. I cannot map this data to C# class because firebase IDs don't comply with the list structure.
Please see my model in C# side;
public class PoolData
{
public string FirebaseId{ get; set; }
public string Alias { get; set; }
public PoolType PoolType { get; set; } //enum
public CoinType CoinType { get; set; } //enum
public string Address { get; set; }
public List<Alarm> Alarms { get; set; }
}
public class Alarm
{
public string FirebaseId{ get; set; }
public AlarmType AlarmType{ get; set; } //enum
public Threshold Threshold { get; set; } //object
}
public class Threshold
{
public ThresholdType Type{ get; set; } //enum
public int Value { get; set; }
}
In order to be able to convert Firebase-ish json into C# object I need a json like this;
{
"PoolDatas": [
{
"FirebaseId": "-LGGJGTAv_DPtzkmjIbl",
"CoinType": 1,
"PoolType": 1,
"Alias": "First Alias",
"Alarms": [
{
"FirebaseId": "-LGsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 1
},
{
"FirebaseId": "-LBAsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 2
}
],
"Address": "0xAE12EF212"
},
{
"FirebaseId": "-LEAJGTAv_DPtzkmjIbl",
"CoinType": 1,
"PoolType": 1,
"Alias": "First Alias",
"Alarms": [
{
"FirebaseId": "-LGsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 1
},
{
"FirebaseId": "-LBAsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 2
}
],
"Address": "0xAE12EF212"
}
]
}
How can I obtain this json from the first one?
I tried several recursive processes using JObject loops but it didn't work.
Thanks in advance!

You can deserialize the JSON to a Dictionary<string, PoolData>, change each value so the key of the dictionary goes into the FirebaseId property, and then put it into an array, something like this:
class Origin
{
public Dictionary<string, PoolData> PoolDatas { get; set; }
}
class Destination
{
public PoolData[] PoolDatas { get; set; }
}
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("data.json");
var data = JsonConvert.DeserializeObject<Origin>(json);
var destination = new Destination();
destination.PoolDatas = data.PoolDatas.Select(i =>
{
i.Value.FirebaseId = i.Key;
return i.Value;
}).ToArray();
}
}
You will need to install and import the Json.Net package using Newtonsoft.Json;.
EDIT: as mentioned in the comments, this will not work for nested objects. For that scenario, maybe you could use a custom serializer, something like this:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ConsoleApp1
{
public interface IFirebaseObject
{
string FirebaseId { get; set; }
}
public class PoolData
{
public string FirebaseId { get; set; }
public string Alias { get; set; }
public string Address { get; set; }
[JsonConverter(typeof(MapToArrayConverter<Alarm>))]
public List<Alarm> Alarms { get; set; }
}
public class Alarm: IFirebaseObject
{
public string FirebaseId { get; set; }
public Threshold Threshold { get; set; } //object
}
public class Threshold
{
public int Value { get; set; }
}
class Origin
{
public Dictionary<string, PoolData> PoolDatas { get; set; }
}
class Destination
{
public PoolData[] PoolDatas { get; set; }
}
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("data.json");
var data = JsonConvert.DeserializeObject<Origin>(json);
var destination = new Destination();
destination.PoolDatas = data.PoolDatas.Select(i =>
{
i.Value.FirebaseId = i.Key;
return i.Value;
}).ToArray();
}
}
public class MapToArrayConverter<T> : JsonConverter where T : IFirebaseObject
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
JObject item = JObject.Load(reader);
Dictionary<string, T> value = JsonConvert.DeserializeObject<Dictionary<string, T>>(item.ToString());
// TODO also consider single values instead of lists
return value.Select(i =>
{
i.Value.FirebaseId = i.Key;
return i.Value;
}).ToList();
} else
{
return null;
}
}
public override bool CanConvert(Type objectType)
{
// TODO validate the object type
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// TODO implement the reverse method to also write
throw new NotImplementedException();
}
}
}
But as you can see the solution starts to get somewhat complex. Unless you need to do this for many types, maybe manually converting would be less time consuming and simpler to maintain.

Related

Flatten nested JSON with JSON.NET in C#

I receive a bill of materials in JSON format via a WebApi, which has a corresponding hierarchy.
The hierarchy or the nesting can be any depth.
An example bill of materials is shown below:
{
"Quantity":0,
"QuantityUnit":"pcs",
"PartNumber":"12345",
"Parent":"",
"Children":[
{
"Quantity":1,
"QuantityUnit":"pcs",
"PartNumber":"88774",
"Parent":"12345",
"Children":[
{
"Quantity":1,
"QuantityUnit":"pcs",
"PartNumber":"42447",
"Parent":"88774"
},
{
"Quantity":0.420,
"QuantityUnit":"kg",
"PartNumber":"12387",
"Parent":"88774"
}
]
}
]
}
How can I resolve this nested structure into a simple structure using JSON.NET in C#?
I want to transform it to:
[
{
"Quantity":0,
"QuantityUnit":"pcs",
"PartNumber":"12345",
"Parent":""
},
{
"Quantity":1,
"QuantityUnit":"pcs",
"PartNumber":"88774",
"Parent":"12345"
},
{
"Quantity":1,
"QuantityUnit":"pcs",
"PartNumber":"42447",
"Parent":"88774"
},
{
"Quantity":0.420,
"QuantityUnit":"kg",
"PartNumber":"12387",
"Parent":"88774"
}
]
For the deserialization I use the following class:
public class Bom
{
public class TopLevel
{
public double Quantity { get; set; }
public string QuantityUnit { get; set; }
public string PartNumber { get; set; }
public string Parent { get; set; }
public List<Item> Children { get; set; }
}
public class Item
{
public double Quantity { get; set; }
public string QuantityUnit { get; set; }
public string PartNumber { get; set; }
public string Parent { get; set; }
}
public double Quantity { get; set; }
public string QuantityUnit { get; set; }
public string PartNumber { get; set; }
public string Parent { get; set; }
public IList<TopLevel> Children { get; set; }
}
Furthermore, I use this code to deserialize the JSON to an object:
Bom bom = JsonConvert.DeserializeObject<Bom>(File.ReadAllText(jsonPath));
First let's define a mapper
JObject Map(JObject source)
{
var result = (JObject)source.DeepClone();
result.Remove("Children");
return result;
}
It simply clones the object and removes the Children property
Next let's define a recursive function to accumulate the JObjects
void Flatten(JArray children, JArray accumulator)
{
if (children == null) return;
foreach (JObject child in children)
{
accumulator.Add(Map(child));
Flatten((JArray)child["Children"], accumulator);
}
}
And finally let's make use of them
var semiParsed = JObject.Parse(json);
var accumulator = new JArray();
accumulator.Add(Map(semiParsed));
Flatten((JArray)semiParsed["Children"], accumulator);
The ToString call on the accumulator will return this
[
{
"Quantity": 0,
"QuantityUnit": "pcs",
"PartNumber": "12345",
"Parent": ""
},
{
"Quantity": 1,
"QuantityUnit": "pcs",
"PartNumber": "88774",
"Parent": "12345"
},
{
"Quantity": 1,
"QuantityUnit": "pcs",
"PartNumber": "42447",
"Parent": "88774"
},
{
"Quantity": 0.42,
"QuantityUnit": "kg",
"PartNumber": "12387",
"Parent": "88774"
}
]
UPDATE #1
If your source json contains a deep hierarchy (lets say more than 5 levels) then the DeepClone is not really efficient, since you are copying the whole subtree.
To fix this problem you just need to rewrite the Map function
JObject Map(JObject source)
=> JObject.FromObject(new
{
Quantity = (double)source["Quantity"],
QuantityUnit = (string)source["QuantityUnit"],
PartNumber = (string)source["PartNumber"],
Parent = (string)source["Parent"]
});
Deserialize the original list, flatten it with Enumerable.SelectMany, and serialize the resulting sequence.

How to deserialize Json into an C# object and fill the components into a dictionary

{
"success": true,
"data": [{
"type": "Employee",
"attributes": {
"id": {
"label": "ID",
"value": 8556527,
"type": "integer",
"universal_id": "id"
},
"email": {
"label": "Email",
"value": "exapmle#gmail.com",
"type": "standard",
"universal_id": "email"
},
"dynamic_2682839": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
}
}
},
public class Id
{
[JsonPropertyName("label")]
public string Label { get; set; }
[JsonPropertyName("value")]
public int Value { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("universal_id")]
public string UniversalId { get; set; }
}
public class Email
{
[JsonPropertyName("label")]
public string Label { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("universal_id")]
public string UniversalId { get; set; }
}
public class Dynamic2682839
{
[JsonPropertyName("label")]
public string Label { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("universal_id")]
public string UniversalId { get; set; }
}
public class Attributes
{
[JsonPropertyName("id")]
public Id Id { get; set; }
[JsonPropertyName("email")]
public Email Email { get; set; }
[JsonPropertyName("dynamic_2682839")]
public Dynamic2682839 Dynamic2682839 { get; set; }
}
public class Datum
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("attributes")]
public Attributes Attributes { get; set; }
}
public class Root
{
[JsonPropertyName("success")]
public bool Success { get; set; }
[JsonPropertyName("data")]
public List<Datum> Data { get; set; }
}
I know it is probably are very simple solution behind it but i cant get to it. My Problem is that I want to deserialize this type of data into an object for example. This json file goes on forever but the only thing I'm interested in are the "ID" and the "Personalnumber" so I can create a dictionary for further processing with my code.
I mean I could just sit there for two days and add everything to the dictionary but first of all it would drive me crazy and second of all the dictionary should add new members automatically to the dictionary and shouldn't be static. I already converted the Json into class objects and downloaded Newtonsoft for my IDE and with this comand:
Id ids = JsonConvert.DeserializeObject<Id>(json);
Console.WriteLine(ids.Value);
I try to get all the values from the ID´s but all i get is a 0.
The thing is i tested everything so far and it works perfectly but my deserialization dont work as planned.
If anyone could help a newbie I would appreciate it.
This looks wrong
Id ids = JsonConvert.DeserializeObject<Id>(json);
You should probably be deserializing to Root instead.
Root root = JsonConvert.DeserializeObject<Root>(json);
From there you can get all the IDs with
List<Id> ids = root.Data.Select(datum => datum.Attributes.Id);
This json file goes on forever but the only thing I'm interested in are the "ID" and the "Personalnumber" ... the dictionary should add new members automatically ... and shouldn't be static.
Phuzi's answer seizes onto the wrong thing, giving you the contents of the "id" object, which does get you the ID, but ignores the Personalnumber..
..which also looks like it might not always be in a dynamic_2682839
We need a different strategy for getting the data; we can look to see if the label contains ID or Personalnumber but we could really do with the serializes deser'ing this data to a common set of objects, not a different Type (with the same properties) each time
I don't know if you used QuickType.IO for generating your JSON classes but, if you didn't, I would recommend it; it's a bit more sophisticated than other generators. You can trick it into helping you more by modifying your JSON..
Take this:
{
"success": true,
"data": [
{
"type": "Employee",
"attributes": {
"id": {
"label": "ID",
"value": 8556527,
"type": "integer",
"universal_id": "id"
},
"email": {
"label": "Email",
"value": "exapmle#gmail.com",
"type": "standard",
"universal_id": "email"
},
"dynamic_2682839": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
}
}
}
]
}
And make it into this (put some more objects in "attributes", and make the keys of the dictionary into sequential numbers):
{
"success": true,
"data": [{
"type": "Employee",
"attributes": {
"1": {
"label": "ID",
"value": 8556527,
"type": "integer",
"universal_id": "id"
},
"2": {
"label": "Email",
"value": "exapmle#gmail.com",
"type": "standard",
"universal_id": "email"
},
"3": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
},
"4": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
},
"5": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
}
}
}]
}
which will help QT see more easily that a Dictionary<string, Attribute> is suitable for "attributes", and a custom converter is needed for the varying type of "value"...
Otherwise you'll just get multiple identical classes (your Id, Email and Dynamic_123 classes) and a property for every member of "attributes" even though they're all the same:
You thus get these classes out of QT:
namespace SomeNamespace
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class SomeRoot
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("data")]
public Datum[] Data { get; set; }
}
public partial class Datum
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("attributes")]
public Dictionary<string, Attribute> Attributes { get; set; }
}
public partial class Attribute
{
[JsonProperty("label")]
public string Label { get; set; }
[JsonProperty("value")]
public Value Value { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("universal_id")]
public string UniversalId { get; set; }
}
public partial struct Value
{
public long? Integer;
public string String;
public static implicit operator Value(long Integer) => new Value { Integer = Integer };
public static implicit operator Value(string String) => new Value { String = String };
}
public partial class SomeRoot
{
public static SomeRoot FromJson(string json) => JsonConvert.DeserializeObject<SomeRoot>(json, SomeNamespace.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this SomeRoot self) => JsonConvert.SerializeObject(self, SomeNamespace.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
ValueConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class ValueConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(Value) || t == typeof(Value?);
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 Value { Integer = integerValue };
case JsonToken.String:
case JsonToken.Date:
var stringValue = serializer.Deserialize<string>(reader);
return new Value { String = stringValue };
}
throw new Exception("Cannot unmarshal type Value");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (Value)untypedValue;
if (value.Integer != null)
{
serializer.Serialize(writer, value.Integer.Value);
return;
}
if (value.String != null)
{
serializer.Serialize(writer, value.String);
return;
}
throw new Exception("Cannot marshal type Value");
}
public static readonly ValueConverter Singleton = new ValueConverter();
}
}
and you can get your ID/Personalnumber attributes like this:
var someRoot = SomeRoot.FromJson(File.ReadAllText("a.json"));
var attribs = someRoot.Data.SelectMany(d => d.Attributes.Where(a => a.Value.Label == "ID" || a.Value.Label.Contains("Personalnumber"))).ToArray();
or some other LINQ of your choosing on the Dictionary at the path "data"."attributes" - here I've chosen to SelectMany; if Data had 10 elements, and each element had 4 Attributes, 2 of which were ID or Personalnumber, you'd get a single array of 20 long, of the ID/Personalnumber Attributes. You might not want to SelectMany, if you want to keep them together (they're only loosely related by ordering in the array after a SelectMany)

Deserializing my JSON into POCO not populating few fields

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

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

Failing to Parse Json to .NET class

Given:
The classes:
public class Venue
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("location")]
public Location Location { get; set; }
}
public class Location
{
public long Lat { get; set; }
public long Lng { get; set; }
public int Distance { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
public class Response
{
private List<Venue> _venues;
[JsonProperty("venues")]
public List<Venue> Venues
{
get { return _venues ?? (_venues = new List<Venue>()); }
set { _venues = value; }
}
}
And a response request:
{
"meta":{
"code":200
},
"response":{
"venues":[
{
"id":"4f96a5aee4b01cb74e4dc3c6",
"name":"Centro",
"contact":{
},
"location":{
"address":"Centro",
"lat":-21.256906640441052,
"lng":-48.31978432813259,
"distance":185,
"city":"Jaboticabal",
"state":"SP",
"country":"Brazil",
"cc":"BR"
},
"canonicalUrl":"https:\/\/foursquare.com\/v\/centro\/4f96a5aee4b01cb74e4dc3c6",
"categories":[
{
"id":"4f2a25ac4b909258e854f55f",
"name":"Neighborhood",
"pluralName":"Neighborhoods",
"shortName":"Neighborhood",
"icon":{
"prefix":"https:\/\/foursquare.com\/img\/categories_v2\/parks_outdoors\/neighborhood_",
"suffix":".png"
},
"primary":true
}
],
"verified":false,
"restricted":true,
"stats":{
"checkinsCount":1106,
"usersCount":86,
"tipCount":0
},
"specials":{
"count":0,
"items":[
]
},
"hereNow":{
"count":0,
"groups":[
]
},
"referralId":"v-1376511204"
},
{
"id":"4c38b0b21a38ef3b56b39221",
"name":"Ice by Nice",
"contact":{
},
"location":{
"address":"Jaboticabal Shopping",
"lat":-21.25513775,
"lng":-48.32320093,
"distance":253,
"city":"Jaboticabal",
"state":"SP",
"country":"Brazil",
"cc":"BR"
},
"canonicalUrl":"https:\/\/foursquare.com\/v\/ice-by-nice\/4c38b0b21a38ef3b56b39221",
"categories":[
{
"id":"4bf58dd8d48988d1c9941735",
"name":"Ice Cream Shop",
"pluralName":"Ice Cream Shops",
"shortName":"Ice Cream",
"icon":{
"prefix":"https:\/\/foursquare.com\/img\/categories_v2\/food\/icecream_",
"suffix":".png"
},
"primary":true
}
],
"verified":false,
"restricted":true,
"stats":{
"checkinsCount":656,
"usersCount":309,
"tipCount":15
},
"specials":{
"count":0,
"items":[
]
},
"hereNow":{
"count":2,
"groups":[
{
"type":"others",
"name":"Other people here",
"count":2,
"items":[
]
}
]
},
"referralId":"v-1376511204"
}
]
}
}
When using JSON.NET like so:
Response response = JsonConvert.DeserializeObject<Response>(jsonString);
The deserialized response object has a empty list of venues, What am I doing wrong?
Thanks
There's a bit of a mismatch in what you're trying to deserialize and the class you have defined to deserialize into. You unfortunately need yet another layer of indirection. Note the response has;
// theres an outer object here which contains response
{
"meta":{ "code":200 },
"response":{
// you're trying to deserialize this, but it's not the entire response, it's a property of an anonymous object
}
}
So if I make a new class;
public class ResponseWrapper
{
public object meta;
public Response response;
}
And instead do;
ResponseWrapper response = JsonConvert.DeserializeObject<ResponseWrapper>(jsonString);
Then it will work.
Note that when you're deserializing using json.NET you have to define a structure that exactly matches the json. In this case you're leaving out the outer most object. It is kind of an annoyance and leads to a lot of code like what I just wrote but that's just the way it goes sometimes.

Categories