我有大约以下图片:
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);
答案 0 :(得分:16)
以下是我对问题解决方案的看法:
Json.Net的自定义反序列化api不透明,即影响我的类层次结构。
实际上,如果您的项目中有10-20个课程,这不是问题,但如果您拥有数千个课程的大型项目,那么您对Json需要遵守OOP设计这一事实并不特别满意。净需求。
Json.Net适用于POCO对象,这些对象在创建后会被填充(初始化)。但在所有情况下都不是真理,有时你会在构造函数中初始化你的对象。要使初始化发生,您需要传递'正确'参数。这些“正确”参数可以在序列化文本中,也可以在之前的某个时间创建和初始化。不幸的是,反序列化期间Json.Net将默认值传递给他不理解的参数,在我的情况下,它总是会导致ArgumentNullException。
这种方法允许在反序列化期间使用序列化或非序列化的任何参数集创建真正的自定义对象,主要问题是该方法次优,它需要每个对象需要自定义反序列化的2个反序列化阶段,但是它可以工作并允许以你需要的方式反序列化对象,所以这里是:
首先,我们按以下方式重新组装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构造函数。
答案 1 :(得分:9)
我不是Json.NET的专家,但AFAIK根本不可能。如果我是你,我会考虑在反序列化之后修复的选项。
很少有序列化API允许您控制构造到那个程度;四种最典型的方法是(最常见的):
听起来你想要的是最后一个,这是非常罕见的。您可能不得不满足于在之外的构造函数。
某些序列化API提供“序列化/反序列化回调”,允许您在各个点(通常在序列化和反序列化之前和之后)对对象运行方法,包括将一些上下文信息传递到回调中。 IF Json.NET支持反序列化回调,这可能是需要考虑的事情。 This question表明可能确实支持[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);
答案 2 :(得分: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
类)。