20

I have a small problem with passing some parent instance to a constructor when deserializing an object with Newtonsoft.Json.

Let's assume i have the following classes

public class A
{
    public string Str1 { get; set; }

    public IList<B> Bs { get; set; }
}

public class B
{
    public B(A a)
    {
        // a should not be null!
        Console.WriteLine(a.Str)
    }
}

And now i serailze and than deserialize the object a like this:

A a = new A()
a.Bs = new List<B>()
a.Bs.Add(new B(a));
a.Bs.Add(new B(a));
a.Bs.Add(new B(a));

var json = JsonConvert.SerializeObject(a);

// Here i need to call the constructor of B when creating new instances
var newA = JsonConvert.DeserializeObject<A>(json);

The problem is, that when deserializing the object, null will be passed to the constructor of B. Does any one has solved this issue/problem before?

Thank you very much!

BendEg
  • 20,098
  • 17
  • 57
  • 131
  • If you really need this, you may want to alter your design. If you can't serialize and then deserialize and object, you shouldn't use it in serialization. – CodeCaster Dec 18 '15 at 08:49
  • I don't get any error message, the passed value is just `null`. Changing the architcture is not really possible, because this would cause a lot of refactoring work. – BendEg Dec 18 '15 at 08:54
  • I mean if your class relies on an internal member, you cannot trivially deserialize it. Serialization by default works on public members only. You might want to make A public, or make it serializable in another way. – CodeCaster Dec 18 '15 at 08:57
  • 1
    Currently i have no internal member. So what do you mean with making `A` public? – BendEg Dec 18 '15 at 09:03
  • What do you do with `A a` in the ctor then? Again, if you need logic to run upon deserialization, the class is probably not viable for using in serialization. Anyway see [How to pass arguments to a non-default constructor?](http://stackoverflow.com/questions/8254503/how-to-pass-arguments-to-a-non-default-constructor) if you really need it and don't want to redesign. – CodeCaster Dec 18 '15 at 09:13
  • 1
    `A` will not only be used in the ctor. I just want to simplify the question. But `A` has some properites and methods, which needs to be accessed from `B` – BendEg Dec 18 '15 at 09:16

2 Answers2

13

In your question and comments you've said that the class B does not have any public property for A. So, when you serialize B, then no A will be written to the JSON, because Json.Net only serializes the public information by default. Therefore, when deserializing, there will not be enough information to recreate B, because there is no A in the JSON. So, step one is making B's reference to A visible to Json.Net. If you don't want to make it public, that is fine, but you will at least need to mark the member with a [JsonProperty] attribute to allow Json.Net to "see" it.

public class B
{
    [JsonProperty]
    private A a;

    public B(A a)
    {
        this.a = a;  // be sure to set the A member in your constructor
    }
}

Now if you do the above you will run into a second problem: your class structure has a reference loop (A has a list of Bs which each refer back to A), and the serializer will throw an exception by default in this case. The solution is to set the serializer's PreserveReferencesHandling setting to Objects (the default is None). This will not only allow the serializer to handle the reference loops during serialization, but will also preserve the original references during deserialization, so that all the Bs will refer to the same A instance. (This is accomplished via special $id and $ref properties that are written into the JSON.)

JsonSerializerSettings settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
};

var json = JsonConvert.SerializeObject(a, settings);

var newA = JsonConvert.DeserializeObject<A>(json, settings);

Working example: https://dotnetfiddle.net/N0FUID

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
3

What I like to do it I have to pass objects in a constructor is to create the object first using my default constructor and then call populate object to set all properties that are not skipped as I decorated the properties with [JsonIgore]

var settings = new JsonSerializerSettings() 
{ 
  Error = HandleJsonDeserializationError,
  PreserveReferencesHandling = PreserveReferencesHandling.Objects 
}
var myObject = new ComplexObject(param1,param2);
JsonConvert.PopulateObject(json, myObject, settings);

You can continue populating objects and deal with any issues if you handle serialisation errors in the JsonSettings property. The signature is as follows:

static void HandleJsonDeserializationError(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs errorArgs)
{
   var currentError = errorArgs.ErrorContext.Error.Message;
   errorArgs.ErrorContext.Handled = true;
   //loging framework logs the error, set brake point etc when debug.
   Logger.Log(currentError, LogLevel.Exceptions);
}
Walter Verhoeven
  • 3,867
  • 27
  • 36