3

Using the depreciated System.Json, I get the Result I expect (coming from Javascript): The Child gets a GrandChild and all the Parents know about it...

var Parents         = new JsonObject();
var Children        = new JsonObject();

var Parent          = JsonArray.Parse("[]");
Parents.Add("1", Parent);

var Child           = JsonArray.Parse("[]");
Children.Add("1", Child);

var DstParent       = (JsonArray)Parents["1"];
DstParent.Add(Children["1"]);


var DstChild    = (JsonArray)Children["1"];
JsonObject GrandChild   = (JsonObject)JsonArray.Parse("{}");
GrandChild.Add("Age", 15);
DstChild.Add(GrandChild);

var Result = Parents.ToString();

Gives me: "{"1":[[{"Age":15}]]}"

Using Newtonsoft.Json 6.0.8, The Parent is not getting the "hint" that it's Child got a GrandChild.

var Parents         = new JObject();
var Children        = new JObject();

var Parent          = JArray.Parse("[]");
Parents.Add("1", Parent);

var Child           = JArray.Parse("[]");
Children.Add("1", Child);

var DstParent       = (JArray)Parents["1"];
DstParent.Add(Children["1"]);


var DstChild    = (JArray)Children["1"];
var GrandChild  = JObject.Parse("{}");
GrandChild.Add("Age", 15);
DstChild.Add(GrandChild);

Gives me: "{"1":[[]]}"

What am I doing wrong?

maol
  • 33
  • 3

1 Answers1

3

The problem arises because all JToken objects have a Parent property which records their location in the JSON object hierarchy -- but you are trying to add your JArray Child to two different unrelated parents. First you add it to the Children object (which is not actually in the tree of JSON objects you are creating):

        Children.Add("1", Child);

Next you add it to the DstParent array (which is in the tree of JSON objects you are creating):

        DstParent.Add(Children["1"]);

So, what does Json.NET do in this case? It could either:

  1. Throw an exception for trying to create a multiply-parented object, OR
  2. Excise the object from its previous parent and move it to its new parent, OR
  3. Create a clone of the object in its new parent.

As it turns out, it takes option #3: it copies Children["1"] into DstParent. I'm not sure if or where this is documented, but it's apparent from the source code for JContainer - look for InsertItem which calls EnsureParentToken. Thus when you add your grandchild to DstChild you are adding it to the original array not the copy. You can see this by adding the following debug code:

        Debug.WriteLine(object.ReferenceEquals(DstParent[0], DstChild)); //prints False

The simple fix for this is to avoid creating the Children object which is completely unnecessary anyway:

        var parentObj = new JObject();

        var parentArray = new JArray();
        parentObj.Add("1", parentArray);

        var childArray = new JArray();
        parentArray.Add(childArray);

        var grandChild = new JObject();
        grandChild.Add("Age", 15);
        childArray.Add(grandChild);
dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thank you for this competent answer. Coming from javascript, adding an object to multiple parents doesn't clone the object. The plan was, to create functions, that do stuff horizontally e.g. for all the parents or all the children. Therefore I added the object to these objects as well... – maol Mar 27 '15 at 05:33
  • 1
    You showed me, that I should get deeper knowledge of c# rather than trying to use c# like javascript. – maol Mar 27 '15 at 05:36
  • Thank you, a little different, but this helped me finally solve adding a nested object without having to deserialize the whole thing. – dsghi Sep 06 '18 at 08:47