How do I parse this Json which is completely variable in structure - c#

I'm trying to parse a section of a json document using System.Text.Json in C# (.NET 6). I have used the following to grab the relevant section from the api response:
string jsonString = await httpClient.GetStringAsync(url);
string fieldsetJsonString = JsonDocument.Parse(jsonString).RootElement.GetProperty("page-content").GetProperty("dialog").GetProperty("content").GetProperty("fieldset").ToString();
I'm left with a string that contains the following structure, where the key/value pairs within "combo","text",etc keys are completely variable, and even those higher level keys are optional:
[
{
"order": 1,
"combo": [
{
"random_string": "random",
"random_bool": true,
"random_int": 1,
"random_list": [
{
"random_string1": "ABC"
},
{
"random_string2": "DEF"
}
]
},
{
"random_string": "random",
"random_bool": true,
"random_int": 1
}
]
},
{
"order": 2,
"text": [
{
"random_string": "random",
"random_bool": true,
"random_int": 1
},
{
"random_string": "random",
"random_bool": true,
"random_int": 1
},
{
"random_string": "random",
"random_bool": true,
"random_int": 1,
"random_object": {
"key1": "TEXT",
"key2": true
}
}
],
"combo": [
{
"random_string": "random",
"random_bool": true,
"random_int": 1,
"random_list": [
{
"random_string1": "ABC"
},
{
"random_string2": "DEF"
}
]
},
{
"random_string": "random",
"random_bool": true,
"random_int": 1
},
{
"random_string": "random",
"random_bool": true,
"random_int": 1,
"random_list": [
{
"random_string1": "ABC"
},
{
"random_string2": "DEF"
}
]
}
],
"comment": [
{
"random_string": "random",
"random_bool": true,
"random_int": 1
},
{
"random_string": "random",
"random_bool": true,
"random_int": 1
}
],
"boolean": [
{
"random_string": "random",
"random_bool": true,
"random_int": 1
},
{
"random_string": "random",
"random_bool": true,
"random_int": 1
}
]
}
]
I've tried to deserialize using the following model:
public class Root
{
public List<Item> fieldset { get; set; }
}
public class Item
{
public Dictionary<string, object> item { get; set; } = new Dictionary<string, object>();
}
var mymodel = JsonSerializer.Deserialize<Root>(fieldsetJsonString);
but I'm getting an error:
System.Text.Json.JsonException: The JSON value could not be converted to MySolution.Models.Root. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
...

The first thing I notice is that your JSON starts with a [ and not {. The one trick to deserializing JSON is you want to start with an assumption about the root, is it an array or an object. In your case, an array, so you're going to deserialize into T[] or IEnumerable<T>.
var fieldSets = JsonSerializer.Deserialize<Dictionary<string, dynamic>[]>(fieldsetJsonString);
This will give you a list of dictionaries. The first will have the keys "order" and "combo", while the second has keys "order", text", "combo", etc..
The value for fieldSets[0]["order"] is going to be a JsonElement with the value of 1. The value for fieldSets[1]["combo"] is going to be a JsonElement with the value of an object as a JsonElement.
Depending on what you want to do next depends on whether JsonElement is the right object for you. If you want to traverse the JSON tree like XML, you might prefer Newtonsoft's Json.NET

Related

Cannot deserialize json object into array

I have this data structure:
public class AutoCompleteObject
{
public Predictions[] predictions { get; set; }
public string status { get; set; }
}
public class Predictions
{
public string description { get; set; }
}
and this is the result of my api:
{
"predictions": [
{
"description": "Hoisberg 142, Hamburg, Germany",
"matched_substrings": [
{
"length": 8,
"offset": 0
},
{
"length": 2,
"offset": 9
},
{
"length": 7,
"offset": 13
}
],
"place_id": "ChIJr_7fvYSKsUcRejFkuj1Nr0o",
"reference": "ChIJr_7fvYSKsUcRejFkuj1Nr0o",
"structured_formatting": {
"main_text": "Hoisberg 143",
"main_text_matched_substrings": [
{
"length": 8,
"offset": 0
},
{
"length": 2,
"offset": 9
}
],
"secondary_text": "Hamburg, Germany",
"secondary_text_matched_substrings": [
{
"length": 7,
"offset": 0
}
]
},
}
],
"status": "OK"
}
I am trying to deserialize into my object using this:
ObservableCollection<AutoCompleteObject> res = JsonConvert.DeserializeObject<ObservableCollection<AutoCompleteObject>>(content);
But it fails stating it was unable to put the string into the array. But the result is an array and can contain many elements. What am I missing?
The exception thrown is the following
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.ObjectModel.ObservableCollection`1[AutoCompleteObject]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To 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.
So the exception point to the problem being that it expects an array. If you look at the json example you provided it starts with { which means it's a json object. To fix it you need to wrap the json object in [] or deserialize to a different class.
For example your json would have to look like this:
[
{
"predictions": [
{
"description": "Hoisberg 142, Hamburg, Germany",
"matched_substrings": [
{
"length": 8,
"offset": 0
},
{
"length": 2,
"offset": 9
},
{
"length": 7,
"offset": 13
}
],
"place_id": "ChIJr_7fvYSKsUcRejFkuj1Nr0o",
"reference": "ChIJr_7fvYSKsUcRejFkuj1Nr0o",
"structured_formatting": {
"main_text": "Hoisberg 143",
"main_text_matched_substrings": [
{
"length": 8,
"offset": 0
},
{
"length": 2,
"offset": 9
}
],
"secondary_text": "Hamburg, Germany",
"secondary_text_matched_substrings": [
{
"length": 7,
"offset": 0
}
]
}
}
],
"status": "OK"
}
]

Deserialize json array in Xamarin [duplicate]

This question already has answers here:
How to auto-generate a C# class file from a JSON string [closed]
(3 answers)
Closed 1 year ago.
I want to Deserialise this json file in my Xamarin android App to only get the coordinates.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "6849033",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
6.562265,
40.36426
],
[
6.5622743,
40.3642745
],
[
6.5622944,
40.3642897
],etc...
Here is my approach with my class
public class Cadastre
{
public List<List<List<List<float>>>> coordinates { get; set; }
public Cadastre()
{
}
}
And finally here my code to Deserialize my json file
string responseFinished = await GetJson();
Cadastre c = JsonConvert.DeserializeObject<Cadastre>(responseFinished);
I tried many solutions but my coordinates are still null.
If anyone has a solution or a lead I would be grateful.
try this
var jsonObject=JObject.Parse(json);
var coordinates = ((JArray)jsonObject["features"][0]["geometry"]["coordinates"][0][0]).Select(c => new { One = c[0], Two = c[1]}).ToList();
result
[
{
"One": 6.562265,
"Two": 40.36426
},
{
"One": 6.5622743,
"Two": 40.3642745
},
{
"One": 6.5622944,
"Two": 40.3642897
}
]
or if you want to deserialize your way
List<List<List<List<float>>>> coordinates = ((JArray)jsonObject["features"][0]["geometry"]["coordinates"]).ToObject<List<List<List<List<float>>>>>();
result
[
[
[
[
6.562265,
40.36426
],
[
6.5622745,
40.364273
],
[
6.5622945,
40.36429
]
]
]
]

How to write the value of one json property in one line?

It is necessary to write value of "Coordinates" property in json without hyphenation for the following lines, not using ToString() (without converting the value to a string). The desired result is shown below.
{
"Id": null,
"Style": "1234",
"Geometry": {
"Type": "Polygon",
"Coordinates": [[[47541.470259278358,6846.8710054924586],[47540.359922950891,6845.4552435801925],[47541.470259278358,6846.8710054924586]]],
"Properties": [
{
"PointType": "Straight"
},
{
"PointType": "Straight"
},
{
"PointType": "Straight"
}
]
}
}
but not:
{
"Id": null,
"Style": "1234",
"Geometry": {
"Type": "Polygon",
"Coordinates": "[[[47541.470259278358,6846.8710054924586],[47540.359922950891,6845.4552435801925],[47541.470259278358,6846.8710054924586]]]",
"Properties": [
{
"PointType": "Straight"
},
{
"PointType": "Straight"
},
{
"PointType": "Straight"
}
]
}
}
A function that serializes the corresponding class object in json:
JToken ToJson()
{
using (var writer = new JTokenWriter()) {
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(writer, this);
return writer.Token;
}
}
it seems your second case contains Coordinates property as serialized string.
why should not use
var string = JsonConvert.SerializeObject(YourObject) ?
But you should install https://www.nuget.org/packages/Newtonsoft.Json/ first
You could use string type for properties where you need double quotes and use array or number if you don't need it

Projection in MongoDB based on Conditions

My document structure in c#:
public class HashTableDocument : Model
{
public int Id { get; set; }
public Dictionary<string, HashSet<int>> items= new Dictionary<string, HashSet<int>>();
}
in Mongo:
{
"_id" : 218,
"items" : {
"1" : [
52711,
201610,
],
"2" : [
246421,
390200
],
"3" : [
105628,
768519
],
"26" : [
17435,
22252,
61389,
65184,
72859,
81421,
931469,
933505,
938377,
959836
],
"27" : [
26917,
38706,
53862,
111816,
827294,
858348,
870334
]
}
}
I want to be able to pass in any List ('x') of Integers to Mongo. And project on only those key value pairs, if values contains any of the integer in given list ('x').
For example, in above document. if i pass List = { 52711, 105628, 17435, 81421} to Mongo then
It should return
{
"_id" : 218,
"items" : {
"1" : [
52711,
201610,
],
"3" : [
105628,
768519
],
"26" : [
17435,
22252,
61389,
65184,
72859,
81421,
931469,
933505,
938377,
959836
],
}
}
because each one of those key's value contains at least one element in it's list.
I don't know the C# syntax, but here's how to do it using the aggregation framework. Note that this uses the $objectToArray expression, introduced in version 3.4.4.
> db.test.aggregate([{
$project: {
x: {
$filter: {
input: {$objectToArray: "$items"},
cond: {
$gt: [
{
$size: {
$filter: {
input: "$$this.v",
as: "int",
cond: {$in: ["$$int", [52711, 105628, 17435, 81421]]}
}
}
},
0
]
}
}
}
}
}])
{
"result": [
{
"_id": 218,
"items": [
{
"k": "1",
"v": [
52711,
201610
]
},
{
"k": "3",
"v": [
105628,
768519
]
},
{
"k": "26",
"v": [
17435,
22252,
61389,
65184,
72859,
81421,
931469,
933505,
938377,
959836
]
}
]
}
],
"ok": 1
}
However, it's generally not easy to do such computation when you have a structure like you do. This aggregation cannot use any indexes to restrict its search. Have you considered using the following schema instead?
{
"_id": 218,
"items": [
{k: "1", v: [52711, 201610]},
{k: "2", v: [246421, 390200]},
{k: "3", v: [105628, 768519]},
{k: "26", v: [17435, 22252, 61389, 65184, 72859, 81421, 931469, 933505, 938377, 959836]},
{k: "27", v: [26917, 38706, 53862, 111816, 827294, 858348, 870334]},
]
}
Then your problem becomes much simpler, and you can do the following instead:
db.test.aggregate([
{$match: {"items.v": {$in: [52711, 105628, 17435, 81421]}}},
{
$project: {
items: {
$filter: {
input: "$items",
cond: {
$size: {
$setIntersection:
[[52711, 105628, 17435, 81421], "$$this.v"]
}
}
}
}
}
}
])
And if you created an index on the field "items.v", the initial $match stage could leverage that index to do a more efficient query.

Deserialize JSON with null values in array

I have the following JSON array, that can also hold null values for some of the elements in the var array. The elements in the var array are always fix.
"vars": [
{
"varCol": [
{
"visible": false,
"val": 1,
},
{
"visible": false,
"val": 5,
},
{
"visible": false,
"val": 5,
},
null
],
"Type": "Type1"
"UniqueId": "ID1"
},
{
"varCol": [
{
"visible": true,
"val": 1,
},
null,
{
"visible": false,
"val": 5,
},
null
],
"Type": "Type2",
"UniqueId": "ID2"
}
]
I have the following C# deserializer classes:
public class Var
{
public int VarId { get; set; }
public string Type { get; set; }
public List<VarCol> VarCols { get; set; }
}
public class VarCol
{
public int VarColId { get; set; }
public bool Visible { get; set; }
public float Val { get; set; }
}
My desired output here is to have an entry in the VarCol that always holds the fixed structure of values in the array. In this case 4 entries in the varCol array for each vars element.
For the deserialization of the JSON I am using:
Var v = JToken.Parse(json_string).ToObject<Var>();
I wanted to insert this as a comment but it is too long so I have to post it as an answer.
Pay attention to commas because your JSON is not valid. I think you want to have something like this:
{
"vars": [
{
"varCol": [
{
"visible": false,
"val": 1
},
{
"visible": false,
"val": 5
},
{
"visible": false,
"val": 5
},
null
],
"Type": "Type1",
"UniqueId": "ID1"
},
{
"varCol": [
{
"visible": true,
"val": 1
},
null,
{
"visible": false,
"val": 5
},
null
],
"Type": "Type2",
"UniqueId": "ID2"
}
]
}
Have a nice day,
Alberto
Not sure what are you trying to achieve, because JToken.Parse() handles null values pretty much the same way as any other object value, so varCol will always have 4 elements.
Here's some test code: http://dotnetfiddle.net/9gGdH9
Sorry, my question was misleading.
The problem I was facing was saving to the DB through Entity Framework a null VarCol in the Var collection. I needed this to be able to know which of the elements in the collection is null, since I have a fixed size list.I solved this by instantiating the null VarCol with an empty VarCol as also Alex Skalozub indicated in one of his comments:
It depends on the way your object is stored in the database. If VarCol
has a many-to-one relation with Var, EF will probably store reference
to Var record in VarCol table. But null record cannot be saved to
database. That's why reading back will bring only those records that
were saved. You probably need to do some post-processing after parsing
JSON to replace null values with empty records before saving to DB. –
Alex Skalozub 5 hours ago

Categories

Resources