Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JObject is losing reference

Tags:

c#

json.net

I am having 2 scinarios to show the issue.

Scenario 1

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        obj["arr"] = arr;       
        arr.Add("mango");
        
        foreach(var a in obj["arr"]){
            Console.WriteLine(a);
        }
        
                            
    }
}

Here obj["array"] should be referenceing the arr, i.e. initialized earlier. So the output should be

apple
mango

but the output was

apple

Scenario 2

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        
        var obj2 = new JObject();
        obj2["arr"] = arr;      
        
        arr.Add("mango");
        
        foreach(var a in obj2["arr"]){
            Console.WriteLine(a);
        }
    }
}

Similarly obj2["arr"] should be referencing the arr. but it is not. So the expected output is

apple
mango

but the output is

apple

I am not that proficient in csharp. Please let me know if i am missing something here.

Edit

Adding another scenario as mentioned by @Wyck in comments.

Scenario 3

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        Console.WriteLine(arr.GetHashCode());
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        Console.WriteLine(obj["arr"].GetHashCode());
        obj["arr"] = arr;   
        Console.WriteLine(obj["arr"].GetHashCode());
        obj["arr"] = arr;   
        Console.WriteLine(obj["arr"].GetHashCode());
        
        arr.Add("mango");
        
        foreach(var a in obj["arr"]){
            Console.WriteLine(a);
        }
        
                            
    }
}

Repeating the assignment obj["arr"] = arr odd number of time gets back the original reference of arr but doing so even number of times doesn't.

The output of this will be

10465620
10465620
1190878
10465620
apple
mango

see the hash code is changed for even number assignment. for the odd number assignment it again became as before.

like image 598
ashutosh Avatar asked Oct 27 '25 06:10

ashutosh


2 Answers

If you look at the source code for Newtonsoft.Json, you will find that when assigning an array to a property, it will create a copy of it:

public JProperty(string name, object? content)
{
    ...
    Value = IsMultiContent(content)
        ? new JArray(content)
        : CreateFromContent(content);
}

The relevent part of JObject is here.

You can easily test this in your code by getting the hash code (.GetHashCode()) of both obj2["arr"] and arr (both of type JArray) and observe that they will be different.

So in order to be able to add to the array, you need to access it via the instance of JObject once the property is assigned, or you can re-assign the array to the property whenever you add an element.

like image 151
Kirk Woll Avatar answered Oct 29 '25 22:10

Kirk Woll


This answer is totally based on @dbc's comment. This surprising behaviour is due to the way Json.net is implemented. The original answer is here.

All JToken needs to have a Parent property and it can have only one Parent.

obj["arr"] = arr;

Here before setting arr, it is verified if arr has a Parent already. if Parent is null, arr will be assigned unmodified, otherwise arr will be cloned and the cloned arr will be assigned. This the reason behind the behaviour of Scenario 1 and Scenario 2.

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        Console.WriteLine(arr.Parent == null ? "null" : arr.Parent.GetHashCode().ToString());
        obj["arr"] = arr;
        Console.WriteLine(arr.Parent == null ? "null" : arr.Parent.GetHashCode().ToString());
        obj["arr"] = arr;   
        Console.WriteLine(arr.Parent == null ? "null" : arr.Parent.GetHashCode().ToString());
        
        arr.Add("mango");
        
        foreach(var a in obj["arr"]){
            Console.WriteLine(a);
        }
        
                            
    }
}

The output of this code block will be

10566368
null
10566368
apple
mango

It can be seen, after even number of assignments the Parent of arr is set to null.

After the first assignment arr.Parent is set (JProperty containing the Name and its Value, which belongs JOject). During the second assignment, as arr already has a Parent, arr will be cloned and the cloned value will be set to the obj["arr"]. As a new JToken is set, Parent of the previous JToken will be set to null i.e. arr.Parent will become null.

Again on third assignment, arr.Parent is null. So it will be set like the first assignment.

for Scenario 2 arr already has a Parent, so during the 2nd assignment i.e. obj2["arr"] = arr;, arr will be cloned and set.

like image 37
ashutosh Avatar answered Oct 29 '25 20:10

ashutosh