Deserializing the following json
{
"MetaData1": "hello world",
"MetaData2": 2022,
"Data": {
"ObjectA": {
"id": 1,
"name": "steve",
"hobbies": 1
},
"ObjectB": {
"id": 2,
"name": "dave",
"age": 55
}
}
}
into corresponding c# objects
public class ObjectBase
{
public int id { get; set; }
public string name { get; set; }
}
public class ObjectA : ObjectBase
{
public int hobbies { get; set; }
}
public class ObjectB : ObjectBase
{
public int age { get; set; }
}
public class Data
{
public ObjectA ObjectA { get; set; }
public ObjectB ObjectB { get; set; }
}
public class Root
{
public string metaData1 { get; set; }
public int metaData2 { get; set; }
public Data Data { get; set; }
}
using
Root object = JsonConvert.DeserializeObject<Root>(json);
How could I search the id properties of the object properties of Root.Data for a matching int and return the corresponding name property.
It would also be useful to be able to create List<ObjectBase> so that other LINQ operations could be performed on these objects.
i think what i am ultimately after here is to end up with List<ObjectBase>.
This can be achieved easily with System.Text.Json (or Newtonsoft).
The most natural representation (IMO) given your Json structure would be to deserialize into a Dictionary<string, ObjectBase>. Then you could convert the dictionary to a List<ObjectBase>. You need a class to match the Data element in your (updated) Json:
// 'root' class to represent the 'Data' element
public class Root
{
public string MetaData1 { get; set; }
public int MetaData2 { get; set; }
public Dictionary<string, ObjectBase> Data { get; set; }
}
// Dictionary<string, ObjectBase>
var model = JsonSerializer.Deserialize<Root>(json);
foreach (var key in model.Data.Keys)
// do something with model.Data[key].id/name
// convert to List<ObjectBase>
var list = new List<ObjectBase>(model.Data.Values);
Expanding on Lasse V. Karlsen's comment, you could instead add all properties to a single class and deserialize into a Dictionary<string, SingleClass>:
public class SingleClass
{
public int id { get; set; }
public string name { get; set; }
public int hobbies { get; set; }
public int age { get; set; }
// all other properties...
}
If you choose this method you may want to consider making the additional properties nullable (if you're interested in differentiating between no hobbies property or someone with hobbies = 0, for example).
The methods above will deserialize into either ObjectBase or the SingleClass.
Demo online
Name only lookup
If you need to look up the name property based on the id then you can do that with the following code:
var semiParsed = JsonConvert.DeserializeObject<Dictionary<string, JObject>>(json);
var name = (from node in semiParsed.Values
let id = (int)node.GetValue("id")
where id == lookupId
select (string)node.GetValue("name"))
.FirstOrDefault();
Console.WriteLine(name);
First we deserialize the json into a collection
The top most properties' name will be the keys of the Dictionary
The top most properties' object will be treated as JObjects (semi parsed jsons)
Then we perform a Linq to Json query
We iterate through the JObjects and retrieve their id property
GetValue returns a JToken and since we know it is a number, we cast it to int
We perform a filtering based on the lookupId
And we select the name property's value
Finally we need to issue a FirstOrDefault method call because the previous query returns an IEnumerable<string>
Here I have assumed that the the id is unique. If the provided lookupId is not defined inside the json then the result will be null.
Wrapping object lookup
If you need to perform a look up for the wrapping object then you need to use Json.NET Schema as well:
var generator = new JSchemaGenerator();
JSchema schemaA = generator.Generate(typeof(ObjectA));
JSchema schemaB = generator.Generate(typeof(ObjectB));
var semiParsed = JsonConvert.DeserializeObject<Dictionary<string, JObject>>(json);
var theNode = (from node in semiParsed.Values
let id = (int)node.GetValue("id")
where id == lookupId
select node)
.FirstOrDefault();
if (theNode == null)
return;
if (theNode.IsValid(schemaA))
{
var objA = theNode.ToObject<ObjectA>();
Console.WriteLine(objA.hobbies);
} else if (theNode.IsValid(schemaB))
{
var objB = theNode.ToObject<ObjectB>();
Console.WriteLine(objB.age);
}
First we generate two json schemas from the class definitions
Then we perform almost the same query the only difference here is the select part
We return here the whole JObject object instead of just its name
Finally perform a schema validation
If the retrieved json matches to schemaA then we can safely convert (ToObject) to ObjectA
We check the json against schemaB as well
the simpliest way would be convert your json to dictionary of JObjects, in this case you don't need any classes at all
var dict = JObject.Parse(json).Properties().ToDictionary(jo => jo.Value["id"],jo=>jo.Value);
var searchId=2;
var name = dict[searchId]["name"]; // dave
or you can deserialize json to list of c# objects
List<ObjectBase> list = JObject.Parse(json).Properties()
.Select(jo => jo.Value.ToObject<ObjectBase>()).ToList();
and use linq to get data
This answer using reflection is poorly optimised as pointed out by serge in a reply to his answer.
foreach (var prop in root.GetType().GetProperties())
{
var obj = prop.GetValue(root);
if ((int) obj.GetType().GetProperty("id").GetValue(obj) == 2)
{
Console.WriteLine(obj.GetType().GetProperty("name").GetValue(obj).ToString());
break;
}
}
Another method that gets List<ObjectBase> using reflection
var objects = root.Data.Objects;
List<ObjectBase> objectList = objects.GetType().GetProperties().ToList<PropertyInfo>().ConvertAll(x => (ObjectBase)x.GetValue(objects));
I am going to leave this answer here incase it helps someone who can't get to this point by desterilisation (maybe their objects weren't crated by desterilisation).
Related
I have a problem when deserializing an object. The object has a property (data) that is a list of JSONElement. I'm doing:
using var doc = JsonDocument.Parse(JsonSerializer.Serialize(result));
var e = doc.RootElement.GetProperty("data");
var data = JsonSerializer.Deserialize<List<MyItem>>(e);
The serialized result variable has the following content:
{
"data":[
{
"id":245,
"number":14,
"name":"Test"
}
],
"totalCount":-1,
"groupCount":-1,
"summary":null
}
And the MyItem class is as follows:
public class MyItem
{
public int Id { get; set; }
public int Number { get; set; }
public string Name { get; set; }
}
The data variable is a list with x items. However all items are empty instances.
What am I doing wrong?
The problem is likely that your data is using lowercase property names which are not translated to the property names in your class with the default deserialization settings.
using System.Text.Json;
dynamic result = new
{
data = new dynamic[] {
new {
id = 245,
number = 14,
name = "Test"
}
},
totalCount = -1,
groupCount = -1
};
using var doc = JsonDocument.Parse(JsonSerializer.Serialize(result));
var e = doc.RootElement.GetProperty("data");
List<MyItem> data = JsonSerializer.Deserialize<List<MyItem>>(e);
Console.WriteLine($"{data.First().Id} {data.First().Number} {data.First().Name}");
The above code won't work with your MyItem class, but try this instead:
public class MyItem
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("number")]
public int Number { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
}
If it works, either use the JsonPropertyName on all your properties or else consider changing your deserialization options.
Everythig can be done in one string of code. You just need to set PropertyNameCaseInsensitive = true of JsonSerializerOptions. Better to make it in a startup file.
List<MyItem> data = JsonDocument.Parse(json).RootElement.GetProperty("data")
.Deserialize<List<MyItem>>();
I have this Json:
{
"UpdatePack":"updatePacks\/1585654836.pack",
"Updates":[
{
"Name":"MsgBoxEx",
"version":"1.5.14.88",
"ChangeLog":"BugFix: Form didn't resize correct.",
"Hash":"5FB23ED83693A6D3147A0485CD13288315F77D3D37AAC0697E70B8F8C9AA0BB8"
},
{
"Name":"Utilities",
"version":"2.5.1.58",
"ChangeLog":"StringManagement updated.",
"Hash":"05E6B3F521225C604662916F50A701E9783E13776DE4FCA27BE4B69705491AC5"
}
]
}
I have created 2 classes to be used to Deserialize it.
class UpdatesList
{
public string Name { get; set; }
public string Version { get; set; }
public string ChangeLog { get; set; }
public string Hash { get; set; }
}
class JsonObjectHolder
{
public string UpdatePack { get; set; }
//public Dictionary<int, MyData> { get; set; }
public Dictionary<int, UpdatesList> Updates { get; set; }
}
But when I try to access the dictionary, I keep getting Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. at " Console.WriteLine(jsonTest.Dict.Count);"
Am I Deserializing it wrong, or do I need to do some thing else to access the result of the dictionary?
I'm new to both C# and Json.
I hope that some one could point me in the right direction on how to handle this.
I'm using Visual Studio 2019 latest update, and .net 4.8.
Regards
/LR
You code doesn't work because 0 and 1 tokens just a properties, not the array items (you don't have square brackets [] around them). You can parse these values to desired structure manually using JObject
var json = JObject.Parse(your_json_string);
var dict = new Dictionary<int, UpdatesList>();
foreach (var item in json.Properties())
{
if (item.Value.Type == JTokenType.Object)
{
var index = int.Parse(item.Name);
var updateList = item.Value.ToObject<UpdatesList>();
dict.Add(index, updateList);
}
}
var holder = new JsonObjectHolder
{
UpdatePack = json["Updates"]?.Value<string>(),
Dict = dict
};
Update: According to OP changes made to JSON it might be deserialized even more simply
var list = json["Updates"]?.ToObject<List<UpdatesList>>();
var holder = new JsonObjectHolder
{
UpdatePack = json["UpdatePack"]?.Value<string>(),
Dict = list.Select((updatesList, index) => new { updatesList, index })
.ToDictionary(x => x.index, x => x.updatesList)
};
The main point here is that Updates is an array of items, not the key-value collection. It can be transformed into Dictionary<int, UpdatesList> using ToDictionary method from System.Linq (or just use List<UpdatesList> as is)
The exception you're getting essentially means the value is being accessed before the object is initialized.
A better, simpler and cleaner way to doing it is using NewtonSoft. (you can easily get it as a Nuget package)
example:
public class Account
{
public string Email { get; set; }
public bool Active { get; set; }
public DateTime CreatedDate { get; set; }
public IList<string> Roles { get; set; }
}
and then usage:
string json = #"{
'Email': 'james#example.com',
'Active': true,
'CreatedDate': '2013-01-20T00:00:00Z',
'Roles': [
'User',
'Admin'
]
}";
Account account = JsonConvert.DeserializeObject<Account>(json);
Console.WriteLine(account.Email);
Source: https://www.newtonsoft.com/json/help/html/DeserializeObject.htm
I don't see why you need Dictionary<int, UpdatesList> Updates, when you can easily just use List<Update> Updates, since your updates are in a JSON array.
I would model your classes like this:
public class Update
{
public string Name { get; set; }
public string Version { get; set; }
public string ChangeLog { get; set; }
public string Hash { get; set; }
}
public class RootObject
{
public string UpdatePack { get; set; }
public List<Update> Updates { get; set; }
}
You can then deserialize with:
JsonConvert.DeserializeObject<RootObject>(json);
Try it out on dotnetfiddle.net
Note: To convert JSON to C# classes, you can go to Edit -> Paste Special -> Paste JSON as Classes inside Visual Studio. Make sure you have copied the JSON to your clipboard before using it. You will get classes similar to above.
your data and the class is not compatible. if you change the string like this it would work.
change "Updates" to "UpdatePack" and add "Dict" around the dictionary items.
{
"UpdatePack":"updates\/4D1D7964D5B88E5867324F575B77D2FA.zip",
"Dict":{
"0":{
"Name":"MsgBoxEx",
"Version":"1.0.123.58",
"ChangeLog":"Bugfix:Form didn't resize correct",
"hash":"AA94556C0D2C8C73DD217974D252AF3311A5BF52819B06D179D17672F21049A6"
},
"1":{
"Name":"Utilities",
"Version":"1.5.321.87",
"ChangeLog":"StringManagement updated",
"hash":"2F561B02A49376E3679ACD5975E3790ABDFF09ECBADFA1E1858C7BA26E3FFCEF"
}
}
}
I am working on WCF and I want to get record list array date wise and I need array key as the date which has common in record like below:
{
"EventAppGetAllSessionByCustomerIdResult":{
"02/22/2017":[
{
"SessionDate":"02/22/2017"
}
],
"08/27/2016":[
{
"SessionDate":"08/27/2016"
}
],
"Status":{
"Description":"Successfull!",
"Status":1
}
}
}
Basically, I want to extract values of SessionDate.
I assumed that you want to extract "SessionDate" property from your JSON. I recommend using JObject.Parse() method.
JObject jObject = JObject.Parse(json);
var result = (JObject)jObject["EventAppGetAllSessionByCustomerIdResult"];
var dates = new List<string>();
foreach(JProperty prop in result.Properties())
{
if (prop.Name != "Status")
{
var values = jObject["EventAppGetAllSessionByCustomerIdResult"][prop.Name].Values<string>("SessionDate");
dates.AddRange(values);
}
}
Little explanation:
In your case "02/22/2017" is property which has an array of objects. Each object has "SessionDate" property which holds value. So, following line will extract values from "SessionDate" of all objects:
var values = jObject["EventAppGetAllSessionByCustomerIdResult"][prop.Name].Values<string>("SessionDate");
values represents all dates from a single property. In your case, it can be from "02/22/2017" or from "08/27/2016".
dates will be list of "SessionDate" values. Of course, you have to handle possible exceptions by yourself.
I'm not sure its what you want but try this as your output object:
public class Session
{
public string SessionDate { get; set; }
}
public class Status
{
public string Description { get; set; }
public int Code { get; set; }
}
public class EventAppGetAllSessionByCustomerIdResult
{
public KeyValuePair<string, Session[]>[] EventAppGetAllSessionByCustomerId { get; set; }
public Status Status { get; set; }
}
I have a windows form application and would like to deserialize a JSON string that I'm getting from a web address so that I can get just two values from it, how would I go about doing this?
Below is the code I have to get the JSON string, and if you go to the URL that it's getting, you can also see the JSON string. I want to just get the item name, and current price of it. Which you can see the price under the current key.
private void GrabPrices()
{
using (WebClient webClient = new System.Net.WebClient())
{
WebClient n = new WebClient();
var json = n.DownloadString("http://services.runescape.com/m=itemdb_rs/api/catalogue/detail.json?item=1513");
string valueOriginal = Convert.ToString(json);
Console.WriteLine(json);
}
}
It's also going to be iterating through a SQLite database and getting the same data for multiple items based on the item ID, which I'll be able to do myself.
EDIT I'd like to use JSON.Net if possible, I've been trying to use it and it seems easy enough, but I'm still having trouble.
Okay so first of all you need to know your JSON structure, sample:
[{
name: "Micheal",
age: 20
},
{
name: "Bob",
age: 24
}]
With this information you can derive a C# object
public class Person
{
public string Name {get;set;}
public int Age {get;set;}
}
Now you can use JSON.NET to deserialize your JSON into C#:
var people = JsonConvert.DeserializeObject<List<Person>>(jsonString);
If you look at the original JSON it is an array of objects, to deal with this I have used List<T>.
Key things to remember, you need to have the C# object mirror in properties that of the JSON object. If you don't have a list, then you don't need List<T>.
If your JSON objects have camel casing, and you want this converted to the C# conventions, then use this:
var people = JsonConvert.DeserializeObject<List<Person>>(
jsonString,
new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
First of all you need to create a class structure for the JSON
public class Wrapper
{
public Item item;
}
public class Item
{
public string icon { get; set; }
public string icon_large { get; set; }
public int id { get; set; }
public string type { get; set; }
public string typeIcon { get; set; }
public string name { get; set; }
public string description { get; set; }
public GrandExchange current { get; set; }
public GrandExchange today { get; set; }
public bool members { get; set; }
public GrandExchange day30 { get; set; }
public GrandExchange day90 { get; set; }
public GrandExchange day180 { get; set; }
}
public class GrandExchange
{
public string trend { get; set; }
public string price { get; set; }
}
Then you need to serialize the current item into a Wrapper class
var wrapper = JsonConvert.DeserializeObject<Wrapper>(json);
Then if you want multiple items in a list, you can do so with this code :
// Items to find
int[] itemIds = {1513, 1514, 1515, 1516, 1517};
// Create blank list
List<Item> items = new List<Item>();
foreach (int id in itemIds)
{
var n = new WebClient();
// Get JSON
var json = n.DownloadString(String.Format("http://services.runescape.com/m=itemdb_rs/api/catalogue/detail.json?item={0}", id));
// Parse to Item object
var wrapper = JsonConvert.DeserializeObject<Wrapper>(json);
// Append to list
items.Add(wrapper.item);
}
// Do something with list
It is also worth noting that Jagex limit how many times this API can be called from a certain IP within a time frame, going over that limit will block your IP for a certain amount of time. (Will try and find a reference for this)
I am accessing an API which is returning JSon in the format:
{"status":1,"complete":1,"list":{"293352541":{"item_id":"293352541","fave":"0"},"247320106":{"item_id":"247320106","fave":"0"},"291842735":{"item_id":"291842735","fave":"0"} .....
The problem I am having is with the number before the item_id tag. It is breaking any attempt I make at deserialising as I cannot represent this random integer in an object that I deserialise in to.
I would expect this number to be, for example, the word "Item", so that it is key representing the enclosed object, but having this number means I cannot make an object representation of the JSon.
So
public class MyClass
{
public string status { get; set; }
public string complete { get; set; }
public List<MyObject> list { get; set; }
}
public class MyObject
{
public string item_id { get; set; }
public string fave { get; set; }
}
then
var items = new JavaScriptSerializer().Deserialize<MyClass>(jsontext);
dersialises, but items.list is empty.
Also,
dynamic result = JSon.Parse(jsontext);
works, but I cannot deserialise or access the list of items in a nice way.
Is there any way to do this? thanks
Because it doesn't require predefined types to deserialize into, you can do this with json.net (also available with nuget). For instance:
var jObj = JObject.Parse(data);
var sense = jObj["list"]
.Select(x => (JProperty)x)
.Select(p => new {
propName = p.Name,
itemId = p.Value["item_id"],
fave = p.Value["fave"]});