如何向非默认构造函数传递参数?

21

我大致有以下这张图片:

public class Foo
{
   public Foo(Bar bar, String x, String y)
   {
       this.Bar = bar;
       this.X = x;
       this.Y = y;
   }

   [JsonIgnore]
   public Bar Bar { get; private set; }

   public String X { get; private set; }
   public String Y { get; private set; }
}

public class Bar
{
    public Bar(String z)
    {
        this.Z = z;
    }

    public String Z { get; private set; }
}

我想在反序列化期间以某种方式将类型为 Bar 的对象传递给类型为 Foo 的构造函数,即:

var bar = new Bar("Hello world");
var x = JsonConvert.DeserializeObject<Foo>(fooJsonString, bar);
3个回答

17
以下是关于问题解决方案的我的想法:
问题:
Json.Net的自定义反序列化API不够透明,即会影响我的类层次结构。
实际上,如果您的项目中有10-20个类,这并不是问题,但是如果您的项目中有成千上万个类,您需要遵守Json.Net要求来符合OOP设计,这将让您很不满意。
Json.Net对POCO对象非常好用,这些对象在创建后进行填充(初始化)。但是,并非所有情况下都是如此,有时您的对象是在构造函数内初始化的。为了使该初始化发生,您需要传递“正确”的参数。这些“正确”的参数可以存在于序列化文本中,或者它们可以在之前某个时刻被创建和初始化。不幸的是,Json.Net在反序列化期间对于它不理解的参数传递默认值,在我的情况下总是导致ArgumentNullException。
解决方案:
这里提供了一种方法,可以在反序列化过程中使用任何一组参数(无论是序列化还是非序列化),实现自定义对象创建,主要问题是这种方法不太优化,每个需要自定义反序列化的对象需要两个反序列化阶段,但是它仍然可行,可以按照您的需求反序列化对象,具体方法如下:
首先,我们将CustomCreationConverter类重组如下:
public class FactoryConverter<T> : Newtonsoft.Json.JsonConverter
{
    /// <summary>
    /// Writes the JSON representation of the object.
    /// </summary>
    /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
    /// <param name="value">The value.</param>
    /// <param name="serializer">The calling serializer.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotSupportedException("CustomCreationConverter should only be used while deserializing.");
    }

    /// <summary>
    /// Reads the JSON representation of the object.
    /// </summary>
    /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
    /// <param name="objectType">Type of the object.</param>
    /// <param name="existingValue">The existing value of object being read.</param>
    /// <param name="serializer">The calling serializer.</param>
    /// <returns>The object value.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        T value = CreateAndPopulate(objectType, serializer.Deserialize<Dictionary<String, String>>(reader));

        if (value == null)
            throw new JsonSerializationException("No object created.");

        return value;
    }

    /// <summary>
    /// Creates an object which will then be populated by the serializer.
    /// </summary>
    /// <param name="objectType">Type of the object.</param>
    /// <returns></returns>
    public abstract T CreateAndPopulate(Type objectType, Dictionary<String, String> jsonFields);

    /// <summary>
    /// Determines whether this instance can convert the specified object type.
    /// </summary>
    /// <param name="objectType">Type of the object.</param>
    /// <returns>
    ///     <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
    /// </returns>
    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    /// <summary>
    /// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
    /// </summary>
    /// <value>
    ///     <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
    /// </value>
    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }
}

接下来我们创建一个工厂类,用于创建 Foo 实例:

public class FooFactory : FactoryConverter<Foo>
{
    public FooFactory(Bar bar)
    {
        this.Bar = bar;
    }

    public Bar Bar { get; private set; }

    public override Foo Create(Type objectType, Dictionary<string, string> arguments)
    {
        return new Foo(Bar, arguments["X"], arguments["Y"]);
    }
}

以下是示例代码:

var bar = new Bar("BarObject");

var fooSrc = new Foo
(
    bar,
    "A", "B"
);

var str = JsonConvert.SerializeObject(fooSrc);

var foo = JsonConvert.DeserializeObject<Foo>(str, new FooFactory(bar));

Console.WriteLine(str);

在这种情况下,foo包含了一个参数,我们需要在反序列化期间传递给Foo构造函数。

10

我不是Json.NET的专家,但据我所知,那是不可能的。如果我是你,我会寻找在反序列化之后修复这个问题的选项。

很少有序列化API允许您控制到那种程度的构造;最常见的四种方法是(最常见的第一种):

  • 调用无参数构造函数
  • 完全跳过构造函数
  • 使用具有明显1:1映射到正在序列化的成员的构造函数
  • 使用用户提供的工厂方法

听起来你想要最后一种,这非常罕见。您可能需要解决在构造函数之外进行此操作的问题。

一些序列化API提供“序列化/反序列化回调”,允许您在对象的各个点上运行方法(通常在序列化和反序列化之前和之后),包括将一些上下文信息传递到回调中。 如果 Json.NET支持反序列化回调,那可能就是需要查看的内容。 这个问题表明[OnDeserialized]回调模式确实得到了支持; context来自您可以选择提供给deserialize方法的JsonSerializerSettings.Context属性。

否则,只需在反序列化后手动运行它。

我的粗略伪代码(完全未经测试):

// inside type: Foo
[OnDeserialized]
public void OnDeserialized(StreamingContext ctx) {
    if(ctx != null) {
        Bar bar = ctx.Context as Bar;
        if(bar != null) this.Bar = bar; 
    }
}

并且

var ctx = new StreamingContext(StreamingContextStates.Other, bar);
var settings = new JsonSerializerSettings { Context = ctx };
var obj = JsonConvert.DeserializeObject<Foo>(fooJsonString, settings);

14
不知道为什么序列化器的编写者们认为这个任务优先级较低。我一直认为,能够构造不可变对象而无需进行底层处理是公共接口序列化器应该具备的基本功能之一。 - CodesInChaos
1
@CodeInChaos protobuf-net支持列出的所有4个选项,以及至少另外一个选项(代理转换)...只是说一下。 - Marc Gravell
@CodeInChaos,至于为什么:简单来说,那真的非常困难。 - Marc Gravell
@Lu4 给添加一个序列化回调函数对我来说似乎不是一项过于繁琐的任务...但这取决于你。我不知道有没有直接实现它的方法,除非篡改 Json.NET 的源代码,但我不建议这样做。 - Marc Gravell
很不幸,在PCL中SerializationContext没有任何属性,尤其是Context属性。我在过去的几天里一直试图实现一种通用的方法来反序列化带有外部上下文的对象,这让我疯狂了。 - Viacheslav Smityukh
显示剩余6条评论

0
如果你有一个构造函数,它的唯一参数是非序列化的值,请先创建实例,然后填充对象,而不是进行反序列化。 JsonConvert 类有一个PopulateObject方法,如下所示:
public static void PopulateObject(
    string value,                      // JSON string
    object target)                     // already-created instance

如果您有特定的序列化设置,还可以包括一个JsonSerializerSettings参数的重载。
添加一个具有单个Bar参数的Foo构造函数,您可以这样做:
var bar = new Bar("Hello World");
var foo = new Foo(bar);
JsonConvert.PopulateObject(fooJsonString, foo);

您可能需要调整您的类以使用字段进行映射,或对NHibernate进行调整以允许写入私有setter(使用自定义的IProxyValidator类)。


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