Deserialized cyclic references are null #1284

Open
Alois-xx opened this Issue Apr 15, 2017 · 3 comments

Comments

Projects
None yet
3 participants
@Alois-xx

Alois-xx commented Apr 15, 2017

I was trying to test the reference handling of JSON.NET and either it is a feature or I have not yet found the right settings for it.
I would expect that when I set
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,

that cyclic references are serialized and deserialized. Serialization works without failure but the the deserialized cyclic field is nulled out. Am I missing something? I am using Nuget package 10.0.2.
`

  [DataContract]
    public class DataForDataContract
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public DataForDataContract Parent;

        [DataMember]
        private string Message;

        public DataForDataContract(int id, string message)
        {
            Id = id;
            Message = message;
        }
    }

    static void JsonNET()
    {
        var ser = JsonSerializer.Create(new JsonSerializerSettings()
        {
            PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
            
        });
        var memory = Memory;
        var parent = new DataForDataContract(1, "Hello");
        parent.Parent = parent;
        var textWriter = new StreamWriter(memory);
        ser.Serialize(textWriter, parent);
        textWriter.Flush();
        memory.Position = 0;
        using (var textReader = new StreamReader(memory))
        {
            var copy = (DataForDataContract)ser.Deserialize(textReader, typeof(DataForDataContract));
            if( copy.Parent == null )
            {
                throw new NullReferenceException("copy.Parent is null");
            }
        }
    }`
@mohannad-abwah

This comment has been minimized.

Show comment
Hide comment
@mohannad-abwah

mohannad-abwah May 16, 2017

I've come across the same problem. Is this a known issue?

It seems to only happen at the top level, since this dotnetfiddle seems to have the cyclical references working fine after the first level. By that I mean the root object's parent field is null, but the childrens' parents fields have proper references.

P.S. I've simplified the demo code a bit.

void Main()
{
	var ser = new JsonSerializerSettings()
	{
		PreserveReferencesHandling = PreserveReferencesHandling.Objects,
		ReferenceLoopHandling = ReferenceLoopHandling.Serialize
	};
	var parent = new DataForDataContract(1, "Hello");
	parent.Parent = parent;
	string json = JsonConvert.SerializeObject(parent, ser); // works great!
	var deserializedObject = JsonConvert.DeserializeObject<DataForDataContract>(json);
	if (deserializedObject.Parent == null)
		throw new NullReferenceException("copy.Parent is null"); // execution reaches here
}

public class DataForDataContract
{
	public int Id { get; set; }
	public DataForDataContract Parent;
	private string Message;
	public DataForDataContract(int id, string message)
	{
		Id = id;
		Message = message;
	}
}

mohannad-abwah commented May 16, 2017

I've come across the same problem. Is this a known issue?

It seems to only happen at the top level, since this dotnetfiddle seems to have the cyclical references working fine after the first level. By that I mean the root object's parent field is null, but the childrens' parents fields have proper references.

P.S. I've simplified the demo code a bit.

void Main()
{
	var ser = new JsonSerializerSettings()
	{
		PreserveReferencesHandling = PreserveReferencesHandling.Objects,
		ReferenceLoopHandling = ReferenceLoopHandling.Serialize
	};
	var parent = new DataForDataContract(1, "Hello");
	parent.Parent = parent;
	string json = JsonConvert.SerializeObject(parent, ser); // works great!
	var deserializedObject = JsonConvert.DeserializeObject<DataForDataContract>(json);
	if (deserializedObject.Parent == null)
		throw new NullReferenceException("copy.Parent is null"); // execution reaches here
}

public class DataForDataContract
{
	public int Id { get; set; }
	public DataForDataContract Parent;
	private string Message;
	public DataForDataContract(int id, string message)
	{
		Id = id;
		Message = message;
	}
}
@AndreasTruetschel

This comment has been minimized.

Show comment
Hide comment
@AndreasTruetschel

AndreasTruetschel Jan 5, 2018

I habe managed to isolate the problem somehow. The problem only occurs if the the type with the cyclic reference (DataForDataContract in the example) has no default constructor. In my research, it did not matter whether the non-parameterless constructor contains a parameter for the cyclic reference itself.
For example, the above example just works fine, if a default constructor is added to the DataForDataContract type.

static void Main()
{
    var ser = new JsonSerializerSettings()
    {
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
    };
    var parent = new DataForDataContract(1, "Hello");
    parent.Parent = parent;
    string json = JsonConvert.SerializeObject(parent, ser); // works great!
    var deserializedObject = JsonConvert.DeserializeObject<DataForDataContract>(json, ser);
    if (deserializedObject.Parent == null)
        throw new NullReferenceException("copy.Parent is null"); // Is not triggered now
}

public class DataForDataContract
{
    public int Id { get; set; }
    public DataForDataContract Parent;
    private string Message;

    public DataForDataContract(int id, string message)
    {
        Id = id;
        Message = message;
    }

    public DataForDataContract()  { } // Default constructor available
}

The example at dotnetfiddle does not work either, if the default constructor is removed. I created a fork here.

EDIT: Forgot to pass serializer settings to JsonConvert.DeserializeObject. Problem is not gone though.

AndreasTruetschel commented Jan 5, 2018

I habe managed to isolate the problem somehow. The problem only occurs if the the type with the cyclic reference (DataForDataContract in the example) has no default constructor. In my research, it did not matter whether the non-parameterless constructor contains a parameter for the cyclic reference itself.
For example, the above example just works fine, if a default constructor is added to the DataForDataContract type.

static void Main()
{
    var ser = new JsonSerializerSettings()
    {
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
    };
    var parent = new DataForDataContract(1, "Hello");
    parent.Parent = parent;
    string json = JsonConvert.SerializeObject(parent, ser); // works great!
    var deserializedObject = JsonConvert.DeserializeObject<DataForDataContract>(json, ser);
    if (deserializedObject.Parent == null)
        throw new NullReferenceException("copy.Parent is null"); // Is not triggered now
}

public class DataForDataContract
{
    public int Id { get; set; }
    public DataForDataContract Parent;
    private string Message;

    public DataForDataContract(int id, string message)
    {
        Id = id;
        Message = message;
    }

    public DataForDataContract()  { } // Default constructor available
}

The example at dotnetfiddle does not work either, if the default constructor is removed. I created a fork here.

EDIT: Forgot to pass serializer settings to JsonConvert.DeserializeObject. Problem is not gone though.

@AndreasTruetschel

This comment has been minimized.

Show comment
Hide comment
@AndreasTruetschel

AndreasTruetschel Jan 5, 2018

Actually it is the object more closer to the root that has to be created with a default constructor in order that all references are set correctly. See this exemple.

static void Main()
{
    var ser = new JsonSerializerSettings()
    {
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
    };
    var b = new B(1, "Hello");

    var a = new A(1.234) { B = b };
    b.As = new List<A> { a };

    var aChild = new AChild(1.45f) { A = a };

    a.Child = aChild;

    string json = JsonConvert.SerializeObject(b, ser); // works great!
    var deserializedObject = JsonConvert.DeserializeObject<B>(json, ser);
    if (deserializedObject.As[0] == null)
        throw new NullReferenceException();

    if (deserializedObject.As[0].B == null)
        throw new NullReferenceException();

    if (deserializedObject.As[0].Child == null)
        throw new NullReferenceException();

    if (deserializedObject.As[0].Child.A == null)
        throw new NullReferenceException();
}

public class B
{
    public int Id { get; set; }
    private string Message;

    public List<A> As;

    public B(int id, string message)
    {
        Id = id;
        Message = message;
    }

    public B() // Must be present to deserialize A -> B
    {

    }
}

public class A
{
    public double D { get; set; }

    public B B { get; set; }

    public AChild Child { get; set; }

    public A(double d)
    {
        D = d;
    }

    public A() // Must be present to deserialize AChild -> A
    {

    }
}

public class AChild
{
    public A A { get; set; }
    public float F { get; set; }

    //public AChild() // Can be removed
    //{

    //}

    public AChild(float f)
    {
        F = f;
    }
}

EDIT: Forgot to pass serializer settings to JsonConvert.DeserializeObject. Problem is not gone though.

AndreasTruetschel commented Jan 5, 2018

Actually it is the object more closer to the root that has to be created with a default constructor in order that all references are set correctly. See this exemple.

static void Main()
{
    var ser = new JsonSerializerSettings()
    {
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
    };
    var b = new B(1, "Hello");

    var a = new A(1.234) { B = b };
    b.As = new List<A> { a };

    var aChild = new AChild(1.45f) { A = a };

    a.Child = aChild;

    string json = JsonConvert.SerializeObject(b, ser); // works great!
    var deserializedObject = JsonConvert.DeserializeObject<B>(json, ser);
    if (deserializedObject.As[0] == null)
        throw new NullReferenceException();

    if (deserializedObject.As[0].B == null)
        throw new NullReferenceException();

    if (deserializedObject.As[0].Child == null)
        throw new NullReferenceException();

    if (deserializedObject.As[0].Child.A == null)
        throw new NullReferenceException();
}

public class B
{
    public int Id { get; set; }
    private string Message;

    public List<A> As;

    public B(int id, string message)
    {
        Id = id;
        Message = message;
    }

    public B() // Must be present to deserialize A -> B
    {

    }
}

public class A
{
    public double D { get; set; }

    public B B { get; set; }

    public AChild Child { get; set; }

    public A(double d)
    {
        D = d;
    }

    public A() // Must be present to deserialize AChild -> A
    {

    }
}

public class AChild
{
    public A A { get; set; }
    public float F { get; set; }

    //public AChild() // Can be removed
    //{

    //}

    public AChild(float f)
    {
        F = f;
    }
}

EDIT: Forgot to pass serializer settings to JsonConvert.DeserializeObject. Problem is not gone though.

AndreasTruetschel added a commit to AndreasTruetschel/Newtonsoft.Json that referenced this issue Jan 5, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment