[英]How to pass arguments to a non-default constructor?
我有大约以下图片:
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);
以下是我对问题解决方案的看法:
Json.Net的自定义反序列化api不透明,即影响我的类层次结构。
实际上,如果您的项目中有10-20个课程,这不是问题,但如果您拥有数千个课程的大型项目,那么您不必特别高兴您需要遵守您的OOP设计和Json.Net要求。
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构造函数。
我不是Json.NET的专家,但AFAIK根本不可能。 如果我是你,我会在反序列化后查看修复此选项的选项。
很少有序列化API允许您控制构造到那个程度; 四种最典型的方法是(最常见的):
听起来你想要最后一个,这是非常罕见的。 你可能不得不在构造函数之外解决这个问题。
一些序列化API提供“序列化/反序列化回调”,允许您在各个点(通常在序列化和反序列化之前和之后)对对象运行方法,包括将一些上下文信息传递到回调中。 如果 Json.NET支持反序列化回调,那可能需要关注。 这个问题表明确实可以支持[OnDeserialized]
回调模式; 的context
来自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);
如果您的构造函数的唯一参数是非序列化值,请先创建实例,然后填充对象而不是反序列化。 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
类)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.