填充重用对象和替换数组的对象?

时间:2017-02-10 17:48:22

标签: c# json.net

使用Json.NET,我想将JObject映射到.NET对象上,行为如下:

  1. 对于JObject的(非空)数组属性,请替换目标对象上的整个集合。

  2. 对于JObject的(非空)对象属性,如果它不为空,则重用目标对象的属性,并仅将提供的属性映射到其上。 / p>

  3. JsonSerializer.Populate似乎就是我想要的,如this answer中所述。至于我正在寻找的行为,似乎我可以通过JsonSerializerSettings.ObjectCreationHandling实现其中一个,但不能同时实现两者。关于需求#1,ObjectCreationHandling.Replace执行我想要的操作,而ObjectCreationHandling.Auto根据需求#2执行我想要的操作,但它会将数组项附加到现有集合上。

    在这里达到这两个要求的推荐方法是什么?

2 个答案:

答案 0 :(得分:2)

一种解决方法是使用自定义JsonConverter,通过在检测到集合类型时忽略现有值来有效地替换集合。

public class ReplaceArrayConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) {
        // check for Array, IList, etc.
        return objectType.IsCollection(); 
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        // ignore existingValue and just create a new collection
        return JsonSerializer.CreateDefault().Deserialize(reader, objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        JsonSerializer.CreateDefault().Serialize(writer, value);
    }
}

像这样使用:

var ser = JsonSerializer.CreateDefault(new JsonSerializerSettings {
    Converters = new[] { new ReplaceArrayConverter() }
});

using (var reader = jObj.CreateReader()) {
    ser.Populate(reader, model);
}

答案 1 :(得分:2)

Json.NET将自动替换任何数组或只读集合。要在反序列化时清除读写集合,可以创建一个custom contract resolver,为每个可修改的集合添加OnDeserializingCallback,在反序列化开始时清除集合。清除集合而不是直接替换集合处理集合是get-only的情况,例如:

public class RootObject
{
    readonly HashSet<int> hashSet = new HashSet<int>();
    public HashSet<int> HashSetValues { get { return this.hashSet; } }
}

合约解决方案如下:

public class CollectionClearingContractResolver : DefaultContractResolver
{
    static void ClearGenericCollectionCallback<T>(object o, StreamingContext c)
    {
        var collection = o as ICollection<T>;
        if (collection == null || collection is Array || collection.IsReadOnly)
            return;
        collection.Clear();
    }

    static SerializationCallback ClearListCallback = (o, c) =>
        {
            var collection = o as IList;
            if (collection == null || collection is Array || collection.IsReadOnly)
                return;
            collection.Clear();
        };

    protected override JsonArrayContract CreateArrayContract(Type objectType)
    {
        var contract = base.CreateArrayContract(objectType);
        if (!objectType.IsArray)
        {
            if (typeof(IList).IsAssignableFrom(objectType))
            {
                contract.OnDeserializingCallbacks.Add(ClearListCallback);
            }
            else if (objectType.GetCollectItemTypes().Count() == 1)
            {
                MethodInfo method = typeof(CollectionClearingContractResolver).GetMethod("ClearGenericCollectionCallback", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
                MethodInfo generic = method.MakeGenericMethod(contract.CollectionItemType);
                contract.OnDeserializingCallbacks.Add((SerializationCallback)Delegate.CreateDelegate(typeof(SerializationCallback), generic));
            }
        }

        return contract;
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static IEnumerable<Type> GetCollectItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }
}

public static class JsonExtensions
{
    public static void Populate<T>(this JToken value, T target) where T : class
    {
        value.Populate(target, null);
    }

    public static void Populate<T>(this JToken value, T target, JsonSerializerSettings settings) where T : class
    {
        using (var sr = value.CreateReader())
        {
            JsonSerializer.CreateDefault(settings).Populate(sr, target);
        }
    }
}

然后使用它,执行:

var settings = new JsonSerializerSettings
{
    ContractResolver = new CollectionClearingContractResolver(),
};
jObject.Populate(rootObject, settings);

示例fiddle

在反序列化在默认构造函数中填充集合的对象时,这样的合同解析器也很有用,如Deserialization causes copies of List-Entries中所示。