I have a JObject that contains json with the same structure as my Device class:
public class Entry
{
public string key { get; set; }
public object value { get; set; }
}
public class Detail
{
public List<Entry> entry { get; set; }
}
public class Device
{
public List<Detail> details { get; set; }
}
I store the json as dynamic:
dynamic d = JsonConvert.DeserializeObject(json);
I want to create a device object from the dynamic object:
new Device { details = d.details }
I can't simply do like that because details is a list that contains a list, and the list has to be created with a constructor, i can't figure out how to do this. Should be something like?
new Device { details = new List<Detail> ( entry = new List<Entry>(some list from d?))}}
Please advice me.
EDIT:
The Json and my object does not share the same structure, it partly does, so i can't simply convert the while json.
If your JSON has exactly the same structure as Device class, then you should be able to just JsonConvert.DeserializeObject<Device>(jsonString).
Even if the original JSON has some more properties, as long as your classes have the same property names, and what's an array's is an array etc. it should work.
Sample:
public class Entry
{
public string key { get; set; }
public object value { get; set; }
}
public class Detail
{
public List<Entry> entry { get; set; }
}
public class Device
{
public List<Detail> details { get; set; }
}
void Main()
{
var json = #"
{
""irrelevant"": ""fnord"",
""also_irrelevant"": [1,3,5,7],
""details"": [
{
""not_entry"": true,
""entry"": [
{
""key"": ""x"",
""value"": ""1""
},
{
""key"": ""y"",
""value"": ""2""
}
]
},
{
""entry"": [
{
""key"": ""a"",
""value"": ""3"",
""bummer"": ""hello""
},
{
""key"": ""b"",
""value"": ""4"",
""bummer"": ""hello""
}
]
}
]
}";
Newtonsoft.Json.JsonConvert.DeserializeObject<Device>(json).Dump();
}
This runs nicely for me in LINQPad, showing resulting Device class with a list of Detail classes (2 entries), each with a list of Entry classes (2 entries each), with the kvp's set.
If you can, I'd go this route. If for some reason naming of the properties is off and you cannot change it, you can always use [JsonProperty("actual_json_name")] to override it.
Related
I exported from Azure IoT Central to a Blob Storage a file containing several JSON objects (36 objects) within that same file.
The below are the first 2 lines from that file
{"applicationId":"appID","component":"thermostat1","deviceId":"usingTemControllerTemplate","enqueuedTime":"2022-03-21T15:31:38.687Z","enrichments":{},"messageProperties":{},"messageSource":"telemetry","schema":"default#v1","telemetry":{"temperature":23.2},"templateId":"urn:modelDefinition:tczx6jwcwz1:h2httvyo48g"}
{"applicationId":"appID","component":"thermostat2","deviceId":"usingTemControllerTemplate","enqueuedTime":"2022-03-21T15:31:38.703Z","enrichments":{},"messageProperties":{},"messageSource":"telemetry","schema":"default#v1","telemetry":{"temperature":16.9},"templateId":"urn:modelDefinition:tczx6jwcwz1:h2httvyo48g"}
I created 2 classes to show the heirarchy in the JSON objects. RootObject & Telemetry.
public class RootObject
{
public string applicationId { get; set; }
public string component { get; set; }
public string deviceId { get; set; }
public string enqueuedTime { get; set; }
public string messageSource { get; set; }
public string schema { get; set; }
public List<Telemetry> telemetry { get; set; }
public string templateId { get; set; }
}
public class Telemetry
{
public double temperature { get; set; }
}
I followed this answer and modeled it to my specific heirarchy and tried to make it work. However, a JsonReaderException is being thrown I run it in Visual Studio.
This is the code I'm running:
using Newtonsoft.Json;
string filePath = "~pathToFile";
RootObject rt = JsonConvert.DeserializeObject<RootObject>(filePath);
if (rt.telemetry[1].temperature == 23.2)
{
Console.WriteLine(rt.telemetry[1].temperature);
}
The JsonReaderException is being thrown on this line:
RootObject rt = JsonConvert.DeserializeObject<RootObject>(filePath);
In the below image is the message being shown:
Could someone please help me find the cause of this issue and how I could resolve it?
This file is not a list/array of objects, it's a 36 lines with each line containing json for a single object.
With this observation we can:
List<RootObject> list = new();
foreach(var line in lines.Where( l => !string.IsNullOrWhiteSpace(l)))
{
RootObject? o = JsonConvert.DeserializeObject<RootObject>(line);
if (o != null)
{
list.Add(o);
}
}
telmetry is an object, not a list so you need to also change the RootObject definition:
"telemetry": {
"temperature": 23.2
},
public class RootObject
{
...
public Telemetry telemetry { get; set; }
first of all the JSON format is wrong, it looks like this (see below) and
secondly he doesn't want to have the File path but the json (value), so you have to read it in
and what is also very important. You have 2 elements of "RootObject",
that means you have to put into a Array or List<> of RootObjects
Code:
using Newtonsoft.Json;
//reads the file and saves into a string
string jsonValue = File.ReadAllText("~pathToFile");
//Deserialize the objects and put them in a list of "RootObject".
List<RootObject> rt = JsonConvert.DeserializeObject<List<RootObject>>(jsonValue);
correct JSON format:
[
{
"applicationId": "appID",
"component": "thermostat1",
"deviceId": "usingTemControllerTemplate",
"enqueuedTime": "2022-03-21T15:31:38.687Z",
"enrichments": {},
"messageProperties": {},
"messageSource": "telemetry",
"schema": "default#v1",
"telemetry": {
"temperature": 23.2
},
"templateId": "urn:modelDefinition:tczx6jwcwz1:h2httvyo48g"
},
{
"applicationId": "appID",
"component": "thermostat2",
"deviceId": "usingTemControllerTemplate",
"enqueuedTime": "2022-03-21T15:31:38.703Z",
"enrichments": {},
"messageProperties": {},
"messageSource": "telemetry",
"schema": "default#v1",
"telemetry": {
"temperature": 16.9
},
"templateId": "urn:modelDefinition:tczx6jwcwz1:h2httvyo48g"
}
]
you have to fix json, by converting it to array of objects
var json = File.ReadAllText(filePath);
json = "[" + json.Replace("\n\r",",")+"]";
List<RootObject> lrt = JsonConvert.DeserializeObject<List<RootObject>>(json);
double[] telemetries=rt.Select(r => r.telemetry.temperature ).ToArray(); // [23.2,16.9]
double telemetry=rt.Where(t=> t.telemetry.temperature==23.2)
.Select(r =>r.telemetry.temperature ).FirstOrDefault(); //23.2
and fix class too, telemetry should be an object, not a list
public class RootObject
{
....
public Telemetry telemetry { get; set; }
}
I know that JsonDotNet can deserialize a json taking care of reference ids. But I couldn't find a solution for a simple array with multiple room ids assigned to a tour (in my example). I set my (de)serializer settings to:
var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };
var data = JsonConvert.DeserializeObject<Data>(json, settings);
My model class looks like:
public class Tour
{
public string Id { get; set; }
public string Name { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public string Id { get; set; }
public string Name { get; set; }
}
}
And my example json like this:
{
"tours":[
{
"$id":"luw5b23b",
"name":"Tour 1",
"rooms":[
{
"$ref":"k2j3hjh2"
},
{
"$ref":"vg2345fg"
},
{
"$ref":"nb2m5h2j"
}
]
}
],
"rooms":[
{
"$id":"k2j3hjh2",
"name":"Room 1"
},
{
"$id":"vg2345fg",
"name":"Room 2"
},
{
"$id":"nb2m5h2j",
"name":"Room 3"
}
]
}
Unfortunatelly the rooms are null. I'm a totally wrong and this is something I should process as ids and link objects after deserialization by myself or can JsonDotNet do that for me?
Please note, the object ids are provided by a system and should not generated or serialized by JsonDotNet.
string json_index = '"libraries": [
{
"name": "test1",
"natives": {
"windows": "natives-windows"
},
"downloads": {
"classifiers": {
"natives-windows": {
"url": "http://test1.com/"
}
}
}
},
{
"name": "test2",
"natives": {
"windows": "natives-windows"
},
"downloads": {
"classifiers": {
"natives-windows": {
"url": "http://test2.com/"
}
}
}
}
]';
dynamic jsonObj = JsonConvert.DeserializeObject(json_index);
foreach (var obj in jsonObj.libraries)
{
label1.Text += "\n" + obj.downloads.classifiers.natives-windows.url; // error here
}
Can not detect the "-" sign between words.
I actually thought that:
string nativeswindows = obj.natives.windows;
label1.Text += "\n" + obj.downloads.classifiers.nativeswindows.url;
but it did not work
How can I get the "url" in "natives-windows" ?
I am using Newtonsoft JSON.
you try:
label1.Text += "\n" + obj.downloads.classifiers["natives-windows"].url;
I found this link: Parsing JSON w/ # at sign symbol in it (arobase)
Hope it will help you!
So there's a few steps to this.
First you need to define a concrete class to represent your JSON. I've done this using http://json2csharp.com, with the output being here:
public class Natives
{
public string windows { get; set; }
}
public class NativesWindows
{
public string url { get; set; }
}
public class Classifiers
{
public NativesWindows __invalid_name__natives-windows { get; set; }
}
public class Downloads
{
public Classifiers classifiers { get; set; }
}
public class Library
{
public string name { get; set; }
public Natives natives { get; set; }
public Downloads downloads { get; set; }
}
public class RootObject
{
public List<Library> libraries { get; set; }
}
Your problematic field has been flagged up by this tool too, seen here:
public NativesWindows __invalid_name__natives-windows { get; set; }
So we need a way to assign the JSON Key/Value pair to a valid C# field. We can does this using Attributes.
For this field in particular, we can use the JsonProperty attribute to take in the JSON property name and assign it to a C# field on your new concrete class. This looks like:
[JsonProperty("native-windows")]
public NativesWindows NativeWindowsObj { get; set; }
You can put that into your new concrete class, and then use the following to deserialize to that type:
Natives jsonObj = JsonConvert.DeserializeObject<Natives>(json_index);
This is telling Newtonsoft:
I have a property name native-windows.
I'm deserializing my JSON to this specific class.
The invalid C# identified native-windows matches a JsonProperty I've specified in my class, assign the value to that matching attribute.
Return the full, deserialized object.
{
"578080": {
"success": true,
"data": {
"type": "game",
"name": "PLAYERUNKNOWN'S BATTLEGROUNDS",
"steam_appid": 578080,
"required_age": 0,
"is_free": false,
}
}
}
This is from the Steam API. As you can see the root key the ID itself, so I don't know how to deserialize this to an object. I've seen other questions regarding unknown property names, but can't seem to apply those solutions for when the root name is unknown.
One way to do this is to Deserialize to Dictionary
Classes
public class Data
{
public string type { get; set; }
public string name { get; set; }
public int steam_appid { get; set; }
public int required_age { get; set; }
public bool is_free { get; set; }
}
public class SomeClass
{
public bool success { get; set; }
public Data data { get; set; }
}
Usage
var result = JsonConvert.DeserializeObject<Dictionary<string, SomeClass>>(json);
If you don't care about making POCO models for your deserialized data and just want to grab some of the properties using a dynamic, you can use JsonExtensionData to get a JToken of the relevant subobject:
public class Foo
{
[JsonExtensionData]
public Dictionary<string, JToken> ExtensionData {get; set;}
}
dynamic obj = JsonConvert.DeserializeObject<Foo>(json).ExtensionData.Single().Value;
Console.WriteLine(obj.success);
Console.WriteLine(obj.data.name);
This approach would be particularly useful if you could reuse Foo across several different types of responses since it doesn't care at all about the object schema.
You can use an anonymous type deserialization to parse JSON data like this, without creating classes. I assumed there is only one Id("578080") present in your data.If more Id's present, you can create an array for those Id's. Hope It Works.
var finalResult=JsonConvert.DeserializeAnonymousType(
yourdata, // input
new
{
Id=
{
new
{
success="", data=""
}
}
}
);
console.write(finalResult.Id);// getting Id 578080
console.write(finalResult.Id.success);
console.write(finalResult.Id.data.type);
Assuming I have a JSON file with the following structure. How I can access the names of the properties in the metadata field.
{
"mappings": {
"basedoc_12kja": {
"properties": {
"created": {
"type": "date",
"format": "dateOptionalTime"
},
"customerID": {
"type": "string"
},
"deleted": {
"type": "boolean"
},
"documentID": {
"type": "string"
},
"id": {
"type": "string"
},
"metadata": {
"properties": {
"Cert": {
"type": "string"
},
"Exp_date": {
"format": "dateOptionalTime"
},
}
}
}
}
}
}
Mappings is an array of documents, each subfield of mappings has a different code. I want to obtain the metadata fields of each document to find out which metadata fields are common between them.
I haven't been able to instantiate this documents.
var response = esReader.GetIndicesMapping();
foreach (var mapping in response.Response.Values)
{
// Parse JSON into dynamic object, convenient!
dynamic results = JObject.Parse(mapping);
List<DocumentType> deserializedObject = JsonConvert.DeserializeObject<List<DocumentType>>(mapping);
}
Exception
{"Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Collections.Generic.List`1[DocumentType]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath 'mappings', line 2, position 14."}
The desire result is to obtain the name of Cert and Exp_date fields
EDIT
public class DocumentType
{
public string Id { set { DocumentID = value; } get { return DocumentID; } }
public string DocumentID { set; get; }
public DateTime Created { set; get; }
.
.
.
public Dictionary<string, object> Metadata { set; get; }
}
The problem here is that your data structure does not match the JSON:
There are no arrays in the JSON. So there is no way you will be able to deserialize that into a C# List.
The "DocumentType" class doesn't match the JSON at all. The class has properties Created, CustomerID, and Deleted which are things like DateTime and string. But the JSON doesn't have those as DateTime or string. They are objects with subproperties named "type" and "format." The property "Metadata" isn't a dictionary: it is an object with a single property named "properties" which should probably be a dictionary.
The case doesn't match.
Don't do that weird thing with Id and DocumentId. The class should match the JSON exactly and literally. No business logic hidden in properties.
The root object has a property called "mappings" so you will need to drill-down before you get to the documents.
Once you successfully get the document, you will need to drill down to the property named "properties" to get to the fields you are interested in.
I suspect there could be multiple documents, and that the "mappings" property contains a list of those documents, where the property names are dynamic and correspond to the name of the document. It is entirely plausible to handle that but not using a deserialization + List approach.
I see 3 approaches here:
Fix the JSON. Not sure if this is possible in your case. If so, start by making mappings hold an array instead of having each document be a property named by the document name.
Fix the deserialization code to match the JSON document. json2csharp did an excellent job so start with that. It just doesn't know that "mappings" is really a Dictionary, not just a thing with a property named "basedoc12_kja."
Don't deserialize it at all. Just query for the metadata. take a look at http://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm which shows several ways to query JSON using JObject properties and LINQ.
Option 1
Example of a slightly cleaned-up JSON if you go that route:
{
"mappings": [
{
"name"" : "basedoc_12kja",
"properties": {
""created": "20150522",
etc.
},
Notice "mappings" is an array and the name became a property of the document. Now you can make a List<> or use JArray. Even better is to get rid of the unused stuff at the top, like this:
[
{
"name" : "basedoc_12kja",
"properties": {
"created"": "20150522",
etc.
},
]
Now it is just an array with no "mappings" at all.
** Option 2 **
Here is code that will do this via deserialization. There are two parts. Step one is to use what json2charp produced. I'll include that here for reference:
public class Created
{
public string type { get; set; }
public string format { get; set; }
}
public class CustomerID
{
public string type { get; set; }
}
public class Deleted
{
public string type { get; set; }
}
public class DocumentID
{
public string type { get; set; }
}
public class Id
{
public string type { get; set; }
}
public class Cert
{
public string type { get; set; }
}
public class ExpDate
{
public string format { get; set; }
}
public class Properties2
{
public Cert Cert { get; set; }
public ExpDate Exp_date { get; set; }
}
public class Metadata
{
public Properties2 properties { get; set; }
}
public class Properties
{
public Created created { get; set; }
public CustomerID customerID { get; set; }
public Deleted deleted { get; set; }
public DocumentID documentID { get; set; }
public Id id { get; set; }
public Metadata metadata { get; set; }
}
public class Basedoc12kja
{
public Properties properties { get; set; }
}
public class Mappings
{
public Basedoc12kja basedoc_12kja { get; set; }
}
public class RootObject
{
public Mappings mappings { get; set; }
}
Then, rename Basedoc12kja to DocumentType, and change RootObject to hold a dictionary. You get this:
public class DocumentType
{
public Properties properties { get; set; }
}
public class RootObject
{
public Dictionary<string, DocumentType> mappings { get; set; }
}
And if you want to get to properties other than just Cert and Exp_date then change Metadata to this:
public class Metadata
{
public Dictionary<string,object> properties { get; set; }
}
Now that can deserialize your document:
JObject results = JObject.Parse(mapping);
RootObject ro = results.ToObject<RootObject>()
You can enumerate through the mappings and get to the properties. They are still messy because of the JSON structure, but you can at least get there.
I hope this helps!
What you have here is a hierarchical dictionary of named properties, where each property can have a type, a format, and possibly a nested dictionary of named child properties -- metadata in your case. You can represent this with the following data model:
[DataContract]
public class PropertyData
{
[DataMember(Name="type", EmitDefaultValue=false)]
public string Type { get; set; }
[DataMember(Name = "format", EmitDefaultValue = false)]
public string Format { get; set; }
[DataMember(Name = "properties", EmitDefaultValue = false)]
public Dictionary<string, PropertyData> Properties { get; set; }
}
[DataContract]
public class Mappings
{
[DataMember(Name = "mappings", EmitDefaultValue = false)]
public Dictionary<string, PropertyData> DocumentMappings { get; set; }
}
(This data model doesn't capture the fact that a given property (probably) can only be a simple type or a complex type with nested properties - but not both. It would seem to suffice for your needs however.)
Then, given the JSON above, you would read it in and convert it to a dictionary of document name to metadata property name as follows:
var mappings = JsonConvert.DeserializeObject<Mappings>(json);
Debug.WriteLine(JsonConvert.SerializeObject(mappings, Formatting.Indented)); // Verify that all was read in.
var metadataNames = mappings.DocumentMappings.ToDictionary(doc => doc.Key, doc => doc.Value.Properties["metadata"].Properties.Select(p => p.Key).ToList());
Debug.WriteLine(JsonConvert.SerializeObject(metadataNames, Formatting.Indented)); // Inspect the resulting mapping table.
And the result is the dictionary of metadata names you want:
{
"basedoc_12kja": [
"Cert",
"Exp_date"
]
}
If you are concerned that the nested metadata might be missing sometimes and so generate NullReferenceExceptions in the query above, you can add null checks as follows:
// Extension methods to query or walk through nested properties, avoiding null reference exceptions when properties are missing
public static class PropertyDataExtensions
{
public static IEnumerable<KeyValuePair<string, PropertyData>> GetProperties(this PropertyData data)
{
if (data == null || data.Properties == null)
return Enumerable.Empty<KeyValuePair<string, PropertyData>>();
return data.Properties;
}
public static PropertyData GetProperty(this PropertyData data, string name)
{
if (data == null || data.Properties == null)
return null;
PropertyData child;
if (!data.Properties.TryGetValue(name, out child))
return null;
return child;
}
}
And then:
var metadataNamesSafe = mappings.DocumentMappings.ToDictionary(doc => doc.Key, doc => doc.Value.GetProperty("metadata").GetProperties().Select(p => p.Key).ToList());