我有一个客户端可以调用两个不同版本的服务。
一个服务仅发送单个值:
{
"value" : { ... }
}
第二个服务始终返回多个值:
{
"values" : [
{ ... },
{ ... }
]
}
理想情况下,我想在我的客户端类中使用单个对象来表示它,这样用户就永远不会看到它是单个值还是多个值。
public class MyValues
{
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
我认为我能够做到这一点的唯一方法是使用我应用于MyValues
的自定义JsonConverter
类,但我真的只想在反序列化属性value
时做一些自定义的事情。 我似乎无法弄清楚 IContractResolver 是否是更好的方法(例如,以某种方式将幻影属性附加到 MyValues 以反序列化value
并将其放入 Values
中。
如果我创建一个自定义转换器,我如何告诉它正常反序列化其他所有内容(例如,如果Other
具有额外的属性,请确保它们得到适当的处理等)
与其编写JsonConverter
,不如在MyValues
上创建一个仅集合属性Value
,如下所示:
public class MyValues
{
[JsonProperty]
Stuff Value
{
set
{
(Values = Values ?? new List<Stuff>(1)).Clear();
Values.Add(value);
}
}
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
如果标有 [JsonProperty]
,它可以是公共的或私有的。 在这种情况下,如果在 JSON 中遇到单例"value"
属性,Json.NET 将调用Value
setter,如果遇到数组"values"
属性,将调用Values
setter。 由于该属性仅设置,因此将仅重新序列化数组属性。
要创建一个自定义JsonConverter
,该对某个类型的一些属性具有特殊处理,但对其余属性使用默认处理,您可以将 JSON 加载到JObject
中,分离并处理自定义属性,然后用 JsonSerializer.Populate()
填充JObject
的其余部分,如下所示:
class MyValuesConverter : CustomPropertyConverterBase<MyValues>
{
protected override void ProcessCustomProperties(JObject obj, MyValues value, JsonSerializer serializer)
{
// Remove the value property for manual deserialization, and deserialize
var jValue = obj.GetValue("value", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();
if (jValue != null)
{
(value.Values = value.Values ?? new List<Stuff>()).Clear();
value.Values.Add(jValue.ToObject<Stuff>(serializer));
}
}
}
public abstract class CustomPropertyConverterBase<T> : JsonConverter where T : class
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var value = existingValue as T ?? (T)contract.DefaultCreator();
ProcessCustomProperties(jObj, value, serializer);
// Populate the remaining properties.
using (var subReader = jObj.CreateReader())
{
serializer.Populate(subReader, value);
}
return value;
}
protected abstract void ProcessCustomProperties(JObject obj, T value, JsonSerializer serializer);
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}