如何将参数传递给反序列化JSON的构造函数

21

使用 Newtonsoft.Json 反序列化对象时,我在将一些父实例传递给构造函数方面遇到了一些小问题。

假设我有以下类:

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)
    }
}

现在我像这样对对象a进行序列化和反序列化:

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);

问题在于,当反序列化对象时,null 将传递给 B 的构造函数。有人之前解决过这个问题吗?

非常感谢!


如果你真的需要这个,你可能需要修改你的设计。如果你不能将一个对象序列化然后再反序列化,那么你就不应该在序列化中使用它。 - CodeCaster
我没有收到任何错误消息,传递的值只是“null”。改变架构并不是真正可行的,因为这将导致大量的重构工作。 - BendEg
我的意思是,如果你的类依赖于一个内部成员,你不能轻易地对其进行反序列化。默认情况下,序列化仅适用于公共成员。你可能想要将A设置为公共,或以其他方式使其可序列化。 - CodeCaster
1
目前我没有内部成员。那么,你说将A公开是什么意思? - BendEg
在构造函数中,你如何处理 A a ?如果你需要在反序列化时运行逻辑,那么该类可能不适合用于序列化。不管怎样,如果你真的需要它而且不想重新设计,请参考如何传递参数给非默认构造函数? - CodeCaster
1
A 不仅仅会在构造函数中使用。我只是想简化问题。但 A 有一些属性和方法,需要从 B 中访问。 - BendEg
2个回答

13

根据你的问题和评论,你说类B没有任何公共属性可以使A可见。因此,在序列化B时,JSON 中不会写入A,因为Json.Net默认只序列化公共信息。因此,在反序列化时,将没有足够的信息来重新创建B,因为JSON中没有A。所以第一步是让BA的引用对Json.Net可见。如果您不想将其设置为公共属性也没关系,但您至少需要使用[JsonProperty]属性标记该成员,以允许Json.Net“看到”它。

public class B
{
    [JsonProperty]
    private A a;

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

如果您按照以上方法操作,您将遇到第二个问题:您的类结构存在引用循环(A有一个B列表,每个B又会引用回A),默认情况下,序列化程序会抛出异常。解决办法是将序列化程序的PreserveReferencesHandling设置为Objects(默认值为None)。这不仅允许序列化程序在序列化期间处理引用循环,还可以在反序列化期间保留原始引用,使所有B都引用同一个A实例(这是通过特殊的$id$ref属性写入JSON中实现的)。

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

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

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

工作示例:https://dotnetfiddle.net/N0FUID


完美地运作,正是我所寻找的!非常感谢! - BendEg
没问题,很高兴能帮助到你。 - Brian Rogers

4

当我需要在构造函数中传递对象时,我喜欢先使用默认构造函数创建对象,然后调用populate object来设置所有未跳过的属性,因为我已经用[JsonIgore]装饰了这些属性。

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

您可以继续填充对象并处理任何问题,如果您在JsonSettings属性中处理序列化错误。签名如下:
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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接