我有一个JSON输入,其对象都是从基类派生的:
public abstract class Base
{
public Base Parent { get; set; }
}
我尝试使用CustomCreationConverter
创建Parent
以将每个对象的ReadJson
属性设置为JSON输入中的父节点(根节点除外)当然)。这可能吗?我不必在创建后遍历对象来设置Parent
属性。
说我有这个输入JSON:
{
"Name": "Joe",
"Children": [
{ "Name": "Sam", "FavouriteToy": "Car" },
{ "Name": "Tom", "FavouriteToy": "Gun" },
]
}
我有以下两个类:
public class Person
{
public Person Parent { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child : Person
{
public string FavouriteToy { get; set; }
}
Name
和FavouriteToy
属性反序列化很好,但我希望将任何Parent
对象的Person
属性设置为,正如您所期望的那样, JSON输入中的实际父对象(可能使用JsonConverter
)。到目前为止我能够实现的最好的是在反序列化之后以递归方式遍历每个对象并以此方式设置Parent
属性。
我想指出,我知道我能够通过JSON内部的引用来做到这一点,但我宁愿避免这种情况。
该问题涉及创建正确派生类的实例,我遇到的问题是在对象的反序列化过程中找到获取上下文的方法。我试图使用JsonConverter的ReadJson方法来设置反序列化对象的属性,以引用同一JSON输入中的另一个对象,不使用使用$ref
s
答案 0 :(得分:2)
我最好的猜测是你在追求这样的事情:
public override bool CanConvert(Type objectType)
{
return typeof(Person).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object value = Activator.CreateInstance(objectType);
serializer.Populate(reader, value);
Person p = value as Person;
if (p.Children != null)
{
foreach (Child child in p.Children)
{
child.Parent = p;
}
}
return value;
}
注意:如果您要对此类进行反序列化(例如在Web应用程序中从http请求反序列化模型),您将获得使用预编译工厂创建对象的更好性能而不是与对象激活器:
object value = serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
请注意,无法访问父级,因为在子级之后始终会创建父级对象。那就是你需要读取对象所包含的json,以便能够完全构造对象,当你读取对象的最后一个括号时,你已经读过它的所有子节点。当孩子解析时,没有父母可以获得参考。
答案 1 :(得分:1)
我遇到了类似的困境。但是,在我的特定场景中,我确实需要属性 Parent
为 readonly
,或者至少为 private set
。因此,@Andrew Savinykh's solution 尽管非常好,但对我来说还不够。因此,我最终结束了将不同方法合并在一起,直到找到可能的“解决方案”或变通方法。
首先,我注意到 JsonSerializer
提供了一个 public readonly Context
属性,该属性可用于在同一个反序列化过程中涉及的实例和转换器之间共享数据。利用这一点,我实现了自己的上下文类,如下所示:
public class JsonContext : Dictionary<string, object>
{
public void AddUniqueRef(object instance)
{
Add(instance.GetType().Name, instance);
}
public bool RemoveUniqueRef(object instance)
{
return Remove(instance.GetType().Name);
}
public T GetUniqueRef<T>()
{
return (T)GetUniqueRef(typeof(T));
}
public bool TryGetUniqueRef<T>(out T value)
{
bool result = TryGetUniqueRef(typeof(T), out object obj);
value = (T)obj;
return result;
}
public object GetUniqueRef(Type type)
{
return this[type.Name];
}
public bool TryGetUniqueRef(Type type, out object value)
{
return TryGetValue(type.Name, out value);
}
}
接下来,我需要将我的 JsonContext
的一个实例添加到我的 JsonSerializerSetttings
中:
var settings = new JsonSerializerSettings
{
Context = new StreamingContext(StreamingContextStates.Other, new JsonContext()),
// More settings here [...]
};
_serializer = JsonSerializer.CreateDefault(settings);
我尝试在 OnDeserializing
和 OnDeserialized
回调中使用此上下文,但是,正如@Andrew Savinykh 所说,它们按以下顺序调用:
Child.OnDeserializing
Child.OnDeserialized
Person.OnDeserializing
Person.OnDeserialized
编辑:在完成我的初始实施(参见解决方案 2)后,我注意到,在使用任何类型的 *CreationConverter
时,上述顺序被修改为如下:
Person.OnDeserializing
Child.OnDeserializing
Child.OnDeserialized
Person.OnDeserialized
我不太确定这背后的原因。这可能与 JsonSerializer
通常使用 Deserialize
的事实有关,它在反序列化回调、实例创建和填充之间从下到上包装对象组合树。相比之下,在使用 CustomCreationConverter
时,序列化器将实例化委托给我们的 Create
方法,然后它可能仅以第二个堆叠顺序执行 Populate
。
如果我们正在寻找更简单的解决方案(请参阅解决方案 1),这种堆叠的回调调用顺序非常方便。利用此版本,我将这种新方法添加在下面的第一位(解决方案 1)和原始的更复杂的方法(解决方案 2)最后。< /p>
与解决方案 2 相比,这可能是一种更简单、更优雅的方法。然而,它不支持通过构造函数初始化 readonly
成员。如果是这种情况,请参阅解决方案 2。
正如我上面所说的,这个实现的一个要求是一个 CustomCreationConverter
以强制以方便的顺序调用回调。例如,我们可以对 PersonConverter
和 Person
使用以下 Child
。
public sealed class PersonConverter : CustomCreationConverter<Person>
{
/// <inheritdoc />
public override Person Create(Type objectType)
{
return (Person)Activator.CreateInstance(objectType);
}
}
然后,我们只需在序列化回调中访问我们的 JsonContext
即可共享 Person Parent
属性。
public class Person
{
public Person Parent { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; }
[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
((JsonContext)context.Context).AddUniqueRef(this);
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
((JsonContext)context.Context).RemoveUniqueRef(this);
}
}
public class Child : Person
{
public string FavouriteToy { get; set; }
[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
Parent = ((JsonContext)context.Context).GetUniqueRef<Person>();
}
}
这是我最初的解决方案。它确实支持通过参数化构造函数初始化 readonly
成员。它可以与解决方案 1 结合使用,将 JsonContext
的用法移至序列化回调。
在我的特定场景中,Person
类缺少无参数构造函数,因为它需要初始化一些 readonly
成员(即 Parent
)。为了实现这一点,我们需要我们自己的 JsonConverter
类,完全基于 CustomCreationConverter
实现,使用带有两个新参数的 abstract T Create
方法:JsonSerializer
,以便提供对我的 JsonContext
和 JObject
,从 reader
预读一些值。
/// <summary>
/// Creates a custom object.
/// </summary>
/// <typeparam name="T">The object type to convert.</typeparam>
public abstract class JObjectCreationConverter<T> : JsonConverter
{
#region Public Overrides JsonConverter
/// <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 => false;
/// <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>
/// <exception cref="NotSupportedException">JObjectCreationConverter should only be used while deserializing.</exception>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException($"{nameof(JObjectCreationConverter<T>)} 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>
/// <exception cref="JsonSerializationException">No object created.</exception>
/// <exception cref="JsonReaderException"><paramref name="reader" /> is not valid JSON.</exception>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// Load JObject from stream
JObject jObject = JObject.Load(reader);
T value = Create(jObject, objectType, serializer);
if (value == null)
{
throw new JsonSerializationException("No object created.");
}
using (JsonReader jObjectReader = jObject.CreateReader(reader))
{
serializer.Populate(jObjectReader, value);
}
return value;
}
/// <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);
}
#endregion
#region Protected Methods
/// <summary>
/// Creates an object which will then be populated by the serializer.
/// </summary>
/// <param name="jObject"><see cref="JObject" /> instance to browse the JSON object being deserialized</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The created object.</returns>
protected abstract T Create(JObject jObject, Type objectType, JsonSerializer serializer);
#endregion
}
注意: CreateReader
是一种自定义扩展方法,它调用默认和无参数的 CreaterReader
,然后从原始 reader
导入所有设置。 See @Alain's response 了解更多详情。
最后,如果我们将此解决方案应用于给定(和自定义)示例:
//{
// "name": "Joe",
// "children": [
// {
// "name": "Sam",
// "favouriteToy": "Car",
// "children": []
// },
// {
// "name": "Tom",
// "favouriteToy": "Gun",
// "children": []
// }
// ]
//}
public class Person
{
public string Name { get; }
[JsonIgnore] public Person Parent { get; }
[JsonIgnore] public IEnumerable<Child> Children => _children;
public Person(string name, Person parent = null)
{
_children = new List<Child>();
Name = name;
Parent = parent;
}
[JsonProperty("children", Order = 10)] private readonly IList<Child> _children;
}
public sealed class Child : Person
{
public string FavouriteToy { get; set; }
public Child(Person parent, string name, string favouriteToy = null) : base(name, parent)
{
FavouriteToy = favouriteToy;
}
}
我们只需添加以下 JObjectCreationConverter
:
public sealed class PersonConverter : JObjectCreationConverter<Person>
{
#region Public Overrides JObjectCreationConverter<Person>
/// <inheritdoc />
/// <exception cref="JsonSerializationException">No object created.</exception>
/// <exception cref="JsonReaderException"><paramref name="reader" /> is not valid JSON.</exception>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object result = base.ReadJson(reader, objectType, existingValue, serializer);
((JsonContext)serializer.Context.Context).RemoveUniqueRef(result);
return result;
}
#endregion
#region Protected Overrides JObjectCreationConverter<Person>
/// <inheritdoc />
protected override Person Create(JObject jObject, Type objectType, JsonSerializer serializer)
{
var person = new Person((string)jObject["name"]);
((JsonContext)serializer.Context.Context).AddUniqueRef(person);
return person;
}
public override bool CanConvert(Type objectType)
{
// Overridden with a more restrictive condition to avoid this converter from being used by child classes.
return objectType == typeof(Person);
}
#endregion
}
public sealed class ChildConverter : JObjectCreationConverter<Child>
{
#region Protected Overrides JObjectCreationConverter<Child>
/// <inheritdoc />
protected override Child Create(JObject jObject, Type objectType, JsonSerializer serializer)
{
var parent = ((JsonContext)serializer.Context.Context).GetUniqueRef<Person>();
return new Child(parent, (string)jObject["name"]);
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
// Overridden with a more restrictive condition.
return objectType == typeof(Child);
}
#endregion
}
public class ContextCreationConverter : JsonConverter
{
#region Public Overrides JsonConverter
/// <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 sealed bool CanWrite => false;
/// <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 sealed bool CanConvert(Type objectType)
{
return false;
}
/// <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>
/// <exception cref="NotSupportedException">ContextCreationConverter should only be used while deserializing.</exception>
public override sealed void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException($"{nameof(ContextCreationConverter)} 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>
/// <exception cref="JsonReaderException"><paramref name="reader" /> is not valid JSON.</exception>
/// <exception cref="JsonSerializationException">No object created.</exception>
public override sealed object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// Load JObject from stream
JObject jObject = JObject.Load(reader);
object value = Create(jObject, objectType, serializer);
using (JsonReader jObjectReader = jObject.CreateReader(reader))
{
serializer.Populate(jObjectReader, value);
}
return value;
}
#endregion
#region Protected Methods
protected virtual object GetCreatorArg(Type type, string name, JObject jObject, JsonSerializer serializer)
{
JsonContext context = (JsonContext)serializer.Context.Context;
if (context.TryGetUniqueRef(type, out object value))
{
return value;
}
if (context.TryGetValue(name, out value))
{
return value;
}
if (jObject.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out JToken jToken))
{
return jToken.ToObject(type, serializer);
}
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
#endregion
#region Private Methods
/// <summary>
/// Creates a instance of the <paramref name="objectType" />
/// </summary>
/// <param name="jObject">
/// The JSON Object to read from
/// </param>
/// <param name="objectType">
/// Type of the object to create.
/// </param>
/// <param name="serializer">
/// The calling serializer.
/// </param>
/// <returns>
/// A new instance of the <paramref name="objectType" />
/// </returns>
/// <exception cref="JsonSerializationException">
/// Could not found a constructor with the expected signature
/// </exception>
private object Create(JObject jObject, Type objectType, JsonSerializer serializer)
{
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
ObjectConstructor<object> creator = contract.OverrideCreator ?? GetParameterizedConstructor(objectType).Invoke;
if (creator == null)
{
throw new JsonSerializationException($"Could not found a constructor with the expected signature {GetCreatorSignature(contract)}");
}
object[] args = GetCreatorArgs(contract.CreatorParameters, jObject, serializer);
return creator(args);
}
private object[] GetCreatorArgs(JsonPropertyCollection parameters, JObject jObject, JsonSerializer serializer)
{
var result = new object[parameters.Count];
for (var i = 0; i < result.Length; ++i)
{
result[i] = GetCreatorArg(parameters[i].PropertyType, parameters[i].PropertyName, jObject, serializer);
}
return result;
}
private ConstructorInfo GetParameterizedConstructor(Type objectType)
{
var constructors = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
return constructors.Length == 1 ? constructors[0] : null;
}
private string GetCreatorSignature(JsonObjectContract contract)
{
StringBuilder sb = contract.CreatorParameters
.Aggregate(new StringBuilder("("), (s, p) => s.AppendFormat("{0} {1}, ", p.PropertyType.Name, p.PropertyName));
return sb.Replace(", ", ")", sb.Length - 2, 2).ToString();
}
#endregion
}
用法:
// For Person we could use any other CustomCreationConverter.
// The only purpose is to achievee the stacked calling order for serialization callbacks.
[JsonConverter(typeof(ContextCreationConverter))]
public class Person
{
public string Name { get; }
[JsonIgnore] public IEnumerable<Child> Children => _children;
[JsonIgnore] public Person Parent { get; }
public Person(string name, Person parent = null)
{
_children = new List<Child>();
Name = name;
Parent = parent;
}
[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
((JsonContext)context.Context).AddUniqueRef(this);
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
((JsonContext)context.Context).RemoveUniqueRef(this);
}
[JsonProperty("children", Order = 10)] private readonly IList<Child> _children;
}
[JsonConverter(typeof(ContextCreationConverter))]
public sealed class Child : Person
{
[JsonProperty(Order = 5)] public string FavouriteToy { get; set; }
public Child(Person parent, string name, string favouriteToy = null) : base(name, parent)
{
FavouriteToy = favouriteToy;
}
}
答案 2 :(得分:0)
我创建了一个例子:
public class Base
{
//This is JSON object's attribute.
public string CPU {get; set;}
public string PSU { get; set; }
public List<string> Drives { get; set; }
public string price { get; set; }
//and others...
}
public class newBase : Base
{
////same
//public string CPU { get; set; }
//public string PSU { get; set; }
//public List<string> Drives { get; set; }
//convert to new type
public decimal price { get; set; } //to other type you want
//Added new item
public string from { get; set; }
}
public class ConvertBase : CustomCreationConverter<Base>
{
public override Base Create(Type objectType)
{
return new newBase();
}
}
static void Main(string[] args)
{
//from http://www.newtonsoft.com/json/help/html/ReadJsonWithJsonTextReader.htm (creadit) + modify by me
string SimulateJsonInput = @"{'CPU': 'Intel', 'PSU': '500W', 'Drives': ['DVD read/writer', '500 gigabyte hard drive','200 gigabype hard drive'], 'price' : '3000', 'from': 'Asus'}";
JsonSerializer serializer = new JsonSerializer();
Base Object = JsonConvert.DeserializeObject<Base>(SimulateJsonInput);
Base converted = JsonConvert.DeserializeObject<Base>(SimulateJsonInput, new ConvertBase());
newBase newObject = (newBase)converted;
//Console.Write(Object.from);
Console.WriteLine("Newly converted atrribute type attribute =" + " " + newObject.price.GetType());
Console.WriteLine("Newly added attribute =" + " " + newObject.from);
Console.Read();
}
希望这会有所帮助。
支持链接:Json .net documentation