Deserialize from minimal JSON to hierachical objects with mandatory back-references - c#

Assume those hierachical object structure: Tree➻Branch➻Leaf
public class Tree
{
public string Name { get; set; }
public List<Branch> Nodes { get; set; } = new List<Branch>();
}
public class Branch
{
public Branch(Tree parent) { Parent = parent; }
public Tree Parent { get; }
public string Name { get; set; }
public List<Leaf> Nodes { get; set; } = new List<Leaf>();
}
public class Leaf
{
public Leaf(Branch parent) { Parent = parent; }
public Branch Parent { get; }
public string Name { get; set; }
}
A not so simple serialization output would be (using ReferenceLoopHandling.Serialize AND PreserveReferencesHandling = PreserveReferencesHandling.Objects):
{
"$id": "1",
"Name": "AnOakTree",
"Nodes": [
{
"$id": "2",
"Parent": {
"$ref": "1"
},
"Name": "TheLowestBranch",
"Nodes": [
{
"$id": "3",
"Parent": {
"$ref": "2"
},
"Name": "OneOfAMillionLeaf"
}
]
}
]
}
Deserialization works perfectly, nothing to complain! But I'd like to omit reference handling at all to reduce JSON footprint and improve readability.
What's the best way to deserialize JSON without reference informations and invoke the constructors with the correct parameter (the instance of the parent node)? So that the back-references will be recreated by code?

Related

How to recursively traverse down a nested object with n levels and refer back to any grandparent level objects on kth level, and change those objects

I need to recurse down an object with arbitrarily deep nesting of child objects. On each level of nesting I reach, I need to refer back to any (potentially all) of the objects on levels I've already been through, i.e. any grandparents of the current child object I am at. I then need to set properties on any of those referenced parent objects based on the current object's state. The properties I'm setting is determined by JSON.
Some values will be determined at runtime (those that are randomly generated in the example below) and others will just be strings from the JSON.
Minimum reproducible example:
Take this JSON as input:
{
"name": "foo",
"targetChildren": [
"baz",
"quux"
],
"valsFromChildren": [],
"runtimeValsFromChildren": [],
"childObjects": [
{
"name": "baz",
"valToGive": "giftFromBaz",
"targetChildren": [
"qux"
],
"valsFromChildren": [],
"runtimeValsFromChildren": [],
"childObjects": [
{
"name": "qux",
"valToGive": "giftFromQux"
},
{
"name": "quux",
"valToGive": "giftFromQuux"
}
]
}
]
}
The targetChildren refer to the names of the child/grandchild objects from which I want to get values and give them to "this" object. The valsFromChildren List should be populated based on that connection - the children with the matching names from targetChildren should put their valToGive value in the valsFromChildren List on the object targeting them. We can assume that there will be no duplicate names.
runtimeValsFromChildren is a List that gets filled with random numbers that are computed by the child object that is giving the value to the parent.
C# console app (needs to be Dotnet version 6):
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Collections;
var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}";
var obj = JsonSerializer.Deserialize<Obj>(json);
DoRecursion(obj);
var newObjJson = JsonSerializer.Serialize(obj);
static Obj DoRecursion(Obj obj)
{
if (obj.ChildObjects == null || obj.ChildObjects.Count <= 0)
return obj;
foreach (var child in obj.ChildObjects)
{
var parent = obj;
if (parent.TargetChildren != null && parent.TargetChildren.Contains(child.Name))
{
// Give the values to the parent that is targeting them.
parent.ValsFromChildren.Add(child.ValToGive);
parent.RuntimeValsFromChildren.Add(child.RuntimeVal);
}
return DoRecursion(child);
}
return obj;
}
class Obj
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("valToGive")]
public string ValToGive { get; set; }
[JsonPropertyName("targetChildren")]
public List<string> TargetChildren { get; set; }
[JsonPropertyName("valsFromChildren")]
public List<string> ValsFromChildren { get; set; }
[JsonPropertyName("runtimeValsFromChildren")]
public List<int> RuntimeValsFromChildren { get; set; }
[JsonPropertyName("childObjects")]
public List<Obj> ChildObjects { get; set; }
[JsonIgnore]
public int RuntimeVal => new Random().Next(0, 100);
}
Desired output in JSON:
{
"name": "foo",
"targetChildren": [
"baz",
"quux"
],
"valsFromChildren": [
"giftFromBaz",
"giftFromQuux"
],
"runtimeValsFromChildren": [
31,
88
],
"childObjects": [
{
"name": "baz",
"valToGive": "giftFromBaz",
"targetChildren": [
"qux"
],
"valsFromChildren": [
"giftFromQux"
],
"runtimeValsFromChildren": [
43
],
"childObjects": [
{
"name": "qux",
"valToGive": "giftFromQux"
},
{
"name": "quux",
"valToGive": "giftFromQuux"
}
]
}
]
}
Actual output:
{
"name": "foo",
"targetChildren": [
"baz",
"quux"
],
"valsFromChildren": [
"giftFromBaz"
],
"runtimeValsFromChildren": [
43
],
"childObjects": [
{
"name": "baz",
"valToGive": "giftFromBaz",
"targetChildren": [
"qux"
],
"valsFromChildren": [
"giftFromQux"
],
"runtimeValsFromChildren": [
60
],
"childObjects": [
{
"name": "qux",
"valToGive": "giftFromQux"
},
{
"name": "quux",
"valToGive": "giftFromQuux"
}
]
}
]
}
I need the valsFromChildren List on the object with name foo to include giftFromQuux (a grand child that I want to grab from the 3rd level down). At the moment it only manages to get the value from its immediate child ("baz"), not its grand child ("quux"). It needs to be a recursive solution that should work for grandparents n levels down the nesting.
I would also like to know how to not mutate the original object but instead return a copy at a different memory address, i.e. have the method not have side effects.
Thanks.
Okay so here is your c# class (I used this website)
public class ChildObject
{
public string name { get; set; }
public string valToGive { get; set; }
public List<string> targetChildren { get; set; }
public List<string> valsFromChildren { get; set; }
public List<int> runtimeValsFromChildren { get; set; }
public List<ChildObject> childObjects { get; set; }
}
public class Root
{
public string name { get; set; }
public List<string> targetChildren { get; set; }
public List<string> valsFromChildren { get; set; }
public List<int> runtimeValsFromChildren { get; set; }
public List<ChildObject> childObjects { get; set; }
}
So you just have to do :
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(theJsonString);
And then when you finish modifying thing do:
string myNewJson = JsonConvert.SerializeObject(myDeserializedClass);
Frankly it is not totally clear on your intent/desired result here SO here is a console app that walks the deserialized and then the altered object a few ways. Perhaps you can build from this a bit. This is a bit "chatty" to give you some ideas where you can go with this.
Here is a working copy of this sample: https://dotnetfiddle.net/qakv6n
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}";
var obj = JsonSerializer.Deserialize<JsonObj>(json);
Console.WriteLine($#"Hello World {obj.Name} has {obj.TargetChildren.Count} targets");
foreach (var item in obj.TargetChildren.Select((value, i) => new { i, value }))
{
Console.WriteLine($#"Hello item: {item} has targets");
var value = item.value;
var index = item.i;
Console.WriteLine($#"x: index:{index} val: {value} ");
}
DoRecursion(obj);
var newObjJson = JsonSerializer.Serialize(obj);
Console.WriteLine($#"serial {newObjJson} ");
foreach (var rv in obj.RuntimeValsFromChildren)
{
Console.WriteLine($#"Count {rv} ");
}
foreach (var v in obj.ValsFromChildren)
{
Console.WriteLine($#"Vals {v} ");
}
}
static JsonObj DoRecursion(JsonObj obj)
{
if (obj.ChildObjects == null || obj.ChildObjects.Count <= 0)
return obj;
foreach (var child in obj.ChildObjects)
{
Console.WriteLine($#"Hello Recursing Name:{child.Name} Count: {obj.ChildObjects.Count} kiddos");
var g = obj.ChildObjects.Where(co => co.Name == child.Name)
.Select(x => new{Name= x.Name, Val= x.ValToGive}).First();
Console.WriteLine($"g:{g.Name} v:{g.Val}");
var newObjJson = JsonSerializer.Serialize(obj);
var parent = obj;
if (parent.TargetChildren != null && parent.TargetChildren.Contains(child.Name))
{
// Give the values to the parent that is targeting them.
parent.ValsFromChildren.Add(child.ValToGive);
parent.RuntimeValsFromChildren.Add(child.RuntimeVal);
}
return DoRecursion(child);
}
return obj;
}
class JsonObj
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("valToGive")]
public string ValToGive { get; set; }
[JsonPropertyName("targetChildren")]
public List<string> TargetChildren { get; set; }
[JsonPropertyName("valsFromChildren")]
public List<string> ValsFromChildren { get; set; }
[JsonPropertyName("runtimeValsFromChildren")]
public List<int> RuntimeValsFromChildren { get; set; }
[JsonPropertyName("childObjects")]
public List<JsonObj> ChildObjects { get; set; }
[JsonIgnore]
public int RuntimeVal => new Random().Next(0, 100);
}
}

Getting NULL Deserializing JSON string using JavaScriptSerializer

Hi I having following JSON but while Using Deserialize method getting NULL in nested Member here is the sample JSON and corresponding Class object:
{
"status": "success",
"Info": [
{
"Name": "1099589",
"version": "Current Version",
"MoreDetails": [
{
"Name": "1099589",
"state": "IN"
},
{
"Name": "1099768",
"state": "OUT"
}
]
},
{
"Name": "1099768",
"version": "2019"
}
],
"errorCode": "",
"message": ""
}
Class :
public class MoreDetail
{
public string Name { get; set; }
public string state { get; set; }
}
public class Info
{
public string Name { get; set; }
public string version { get; set; }
public IList<MoreDetail> MoreDetails { get; set; }
}
public class Example
{
public string status { get; set; }
public IList<Info> Info { get; set; }
public string errorCode { get; set; }
public string message { get; set; }
}
While I am using
JavaScriptSerializer js = new JavaScriptSerializer();
Example ex = new OfferingPayload();
ex = js.Deserialize<Example> (jsonstring);
I am able to see Example object having Info data as list but MoreDetails member of Info Class is coming NULL.
Can someone suggest what I am missing here ?
Thats because your second "Info" object doesnt have "MoreDetails" property.
{
"Name": "1099768",
"version": "2019"
}
To make it works you can add an empty "MoreDetails" property to your json.
{
"Name": "1099768",
"version": "2019",
"MoreDetails": []
}
Or you can configure your serializer to handle this property as optional. Then it will works fine even if it missing in your json.

Automapper and use a alternative key

How can I map two objects like
class Parent
{
public Guid Id { get; set; }
public IEnumerable<Child> Childrens { get; set; }
}
class Child
{
public Guid Id { get; set; }
public int AlternativeKey { get; set; }
}
Parent1
{
"Id": "1367debe-7d88-41c7-b152-0ff0747e5d4b",
"Children": [{
"Id":"8d1377b7-1190-48b1-aa21-53a2eb95837b",
"AlternativeKey":"13",
"Name":""
},
{
"Id":"ed3eea42-4ff3-40f0-9b84-7f8c4ae6f603",
"AlternativeKey":"14",
"Name":""
}
]
}
Parent2
{
"Id": "00000000-0000-0000-0000-000000000000",
"Children": [{
"Id":"00000000-0000-0000-0000-000000000000",
"AlternativeKey":"13",
"Name":"ABC"
},
{
"Id":"00000000-0000-0000-0000-000000000000",
"AlternativeKey":"14",
"Name":"Foo"
}
]
}
And now I want to merge these with automapper. And the result should be the name from Parent2 in Parent1. My problem is to use AlternativeKey as mapping primary key instead of Id.
Expected result
{
"Id": "1367debe-7d88-41c7-b152-0ff0747e5d4b",
"Children": [{
"Id":"8d1377b7-1190-48b1-aa21-53a2eb95837b",
"AlternativeKey":"13",
"Name":"ABC"
},
{
"Id":"ed3eea42-4ff3-40f0-9b84-7f8c4ae6f603",
"AlternativeKey":"14",
"Name":"Foo"
}
]
}

How to Deserialize JSON array(or list) in C#

public static string DeserializeNames()
{
// Json I am passing for the deserialization.
JsonStream= "{
"head": {
"Rows": [
"test 1",
[
[
{
"#Key": "Domain",
"#value": "LocalHost"
},
{
"#Key": "Cookie name(s)",
"#value": "Google"
},
{
"#Key": "Purpose",
"#value": "Test"
},
{
"#Key": "lifetime",
"#value": "test"
}
]
]
]
}
}"
//deserialize JSON from file
JavaScriptSerializer serializer = new JavaScriptSerializer();
var cookieList = serializer.Deserialize<List<Cookie>>(JsonStream).ToList();
}
//Class descriptions
//I have created below classes for the deserialization. Records are not deserialized i am getting zero record count.
public class Categorization
{
public string categorizationName { get; set; }
public List<object> KeyValue{ get; set; }
}
public class Head
{
public IList<Categorization> Rows { get; set; }
}
public class Cookie
{
public Head head { get; set; }
}
Also created below set of the classes and tried the deserialization, Still no luck
public class Head
{
public List<object> Rows { get; set; }
}
public class Cookie
{
public Head head { get; set; }
}
I am getting count as 0 i am not able to fetch any record.
Please help !!
I have modified the Json as below and stored in in the file "test.json" :
{
"head": {
"TestRows": [
[
{
"Key": "Domain",
"value": "Google"
},
{
"Key": "Cookie",
"value": "Test"
},
{
"Key": "Use for",
"value": "Test."
},
{
"Key": "Expire Time",
"value": "1 hour"
}
]
]
}
}
And created below set of classes :
public class Head
{
public IList<List<Dictionary<string, object>>> TestRows{ get; set; }
}
public class Cookie
{
public Head Head { get; set; }
}
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var path= Path.Combine(baseDirectory, "test.json");
//deserialize JSON from file
string JsonStream = System.IO.File.ReadAllText(path, Encoding.Default);
var DeserializedCookieList = JsonConvert.DeserializeObject<Cookie>(JsonStream);
Deserialization is working properly.

How can I use ReadAsAsync<T> with this data schema?

I am using System.Net.Http.HttpClient, the version currently available in NuGet,
to retrieve data from a service in json format. The data roughly looks like this:
{
"schema": "Listing",
"data": {
"key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
"children": [{
"kind": "type1",
"data": {
"body": "Four score and seven years ago...",
"parent_id": "2qh3l",
"report_count": 0,
"name": "c4j6yeh"
}
}, {
"kind": "type3",
"data": {
"domain": "abc.def.com",
"flagged": true,
"category": "news",
"saved": false,
"id": "t3dz0",
"created": 1335998011.0
}
}]
}
}
I use HttpContentExtensions.ReadAsAsync<T> to de-serialize that json string into an object graph. The type definitions looks roughly like this:
public class Response
{
public String schema { get;set; }
public ListingData data { get;set; }
}
public class ListingData
{
public string key { get;set; }
public List<OneItem> children { get;set; }
}
Here's the problem: I desire the type of the items in children to vary depending on the kind property. If kind is "type1" then I want to de-serialize an object of... let's call it Type1 . If kind is "type3" then I want an object of type Type3.
Right now, I can deserialize a List<Type1> or a List<Type3>, but I don't know how to tell the de-serialization logic to distinguish between the two.
I could merge all the properties of the "type1" data object and the "type3" data object into a single .NET Type. But the number of properties is large enough that this gets messy.
If the name of the property in the JSON (in this case data) were different, I could distinguish using that. If, for example, the data looked like this:
"children": [{
"kind": "type1",
"t1data": { ... }
}, {
"kind": "type3",
"t3data": { ... }
}]
...then I could do something like this in .NET:
public class OneItem
{
public string kind { get;set; }
public Type1 t1data { get;set; }
public Type3 t3data { get;set; }
}
But my data schema doesn't look like that.
Is it possible to choose the type for de-serialization by the content of the data? In other words,
look at the value of one property (in this case, kind) to determine how to de-serialize the content for another property (in this case, data).
Or is it possible to inject a filter or transformer that acts on the JSON before ReadAsAsync tries to deserialize it?
If so, How?
If you're ok w/ doing some pre-processing on your response and you can use Json.NET, you should be able to do what you want.
Given the following classes:
public class Response
{
public string schema
{
get;
set;
}
public ListingData data
{
get;
set;
}
}
public class ListingData
{
public string key
{
get;
set;
}
public List<object> children
{
get;
set;
}
}
public class Type1
{
public string body
{
get;
set;
}
public string parent_id
{
get;
set;
}
public int report_count
{
get;
set;
}
public string name
{
get;
set;
}
}
public class Type3
{
public string domain
{
get;
set;
}
public bool flagged
{
get;
set;
}
public string category
{
get;
set;
}
public bool saved
{
get;
set;
}
public string id
{
get;
set;
}
public double created
{
get;
set;
}
}
This test passes:
[Test]
public void RoundTrip()
{
var response = new Response
{
schema = "Listing",
data = new ListingData
{
key = "28ba648c-de24-45d4-a7d9-70f810cf5438",
children = new List<object>
{
new Type1
{
body = "Four score and seven years ago...",
parent_id = "2qh3l",
report_count = 0,
name = "c4j6yeh"
},
new Type3
{
domain = "abc.def.com",
flagged = true,
category = "news",
saved = false,
id = "t3dz0",
created = 1335998011.0
}
}
}
};
var jsonSerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Objects
};
string serializedResponse = JsonConvert.SerializeObject(response, jsonSerializerSettings);
Console.WriteLine(serializedResponse);
var roundTrippedResponse = JsonConvert.DeserializeObject<Response>(serializedResponse, jsonSerializerSettings);
Assert.That(roundTrippedResponse.data.children.First().GetType(), Is.EqualTo(typeof(Type1)));
Assert.That(roundTrippedResponse.data.children.Last().GetType(), Is.EqualTo(typeof(Type3)));
}
The output written to the console is:
{
"$type": "Test.Response, Test",
"schema": "Listing",
"data": {
"$type": "Test.ListingData, Test",
"key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
"children": [
{
"$type": "Test.Type1, Test",
"body": "Four score and seven years ago...",
"parent_id": "2qh3l",
"report_count": 0,
"name": "c4j6yeh"
},
{
"$type": "Test.Type3, Test",
"domain": "abc.def.com",
"flagged": true,
"category": "news",
"saved": false,
"id": "t3dz0",
"created": 1335998011.0
}
]
}
}
So if you can transform your received response to match that of Json.NET's expected format, this will work.
To piece all of this together, you would need to write a custom MediaTypeFormatter and pass it to the ReadAsAsync<>() call.

Categories

Resources