首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Json.Net PopulateObject -基于ID的更新列表元素

Json.Net PopulateObject -基于ID的更新列表元素
EN

Stack Overflow用户
提问于 2016-01-06 08:50:18
回答 1查看 1.7K关注 0票数 6

可以定义用于JsonConvert.PopulateObject方法的自定义"list-merge“启动吗?

示例:

我有两个模特:

代码语言:javascript
运行
复制
class Parent
{
    public Guid Uuid { get; set; }

    public string Name { get; set; }

    public List<Child> Childs { get; set; }
}

class Child 
{
    public Guid Uuid { get; set; }

    public string Name { get; set; }

    public int Score { get; set; }
}

我最初的JSON:

代码语言:javascript
运行
复制
{  
   "Uuid":"cf82b1fd-1ca0-4125-9ea2-43d1d71c9bed",
   "Name":"John",
   "Childs":[  
      {  
         "Uuid":"96b93f95-9ce9-441d-bfb0-f44b65f7fe0d",
         "Name":"Philip",
         "Score":100
      },
      {  
         "Uuid":"fe7837e0-9960-4c45-b5ab-4e4658c08ccd",
         "Name":"Peter",
         "Score":150
      },
      {  
         "Uuid":"1d2cdba4-9efb-44fc-a2f3-6b86a5291954",
         "Name":"Steve",
         "Score":80
      }
   ]
}

我的更新JSON:

代码语言:javascript
运行
复制
{  
   "Uuid":"cf82b1fd-1ca0-4125-9ea2-43d1d71c9bed",
   "Childs":[  
      {  
         "Uuid":"fe7837e0-9960-4c45-b5ab-4e4658c08ccd",
         "Score":170
      }
   ]
}

我所需要的只是指定一个模型属性(by属性),用于匹配列表项(在我的例子中是Uuid属性),因此用更新JSON调用从初始JSON反序列化的对象上的JsonConvert.PopulateObject (它只包含更改的值+每个对象的Uuid )结果,只更新Uuid所包含的更新JSON中包含的列表元素(在我的例子中是Uuid的分数更新),以及没有包含在update JSON中的元素。

我正在寻找一些通用的解决方案--我需要将它应用于具有大量嵌套列表的大型JSON上(但每个模型都有一些独特的属性)。因此,我需要递归调用匹配列表项上的PopulateObject

EN

回答 1

Stack Overflow用户

发布于 2016-01-06 17:10:18

您可以创建自己的JsonConverter来实现所需的合并逻辑。这是可能的,因为JsonConverter.ReadJson传递了一个existingValue参数,该参数包含正在反序列化的属性的预先存在的内容。

因此:

代码语言:javascript
运行
复制
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class JsonMergeKeyAttribute : System.Attribute
{
}

public class KeyedListMergeConverter : JsonConverter
{
    readonly IContractResolver contractResolver;

    public KeyedListMergeConverter(IContractResolver contractResolver)
    {
        if (contractResolver == null)
            throw new ArgumentNullException("contractResolver");
        this.contractResolver = contractResolver;
    }

    static bool CanConvert(IContractResolver contractResolver, Type objectType, out Type elementType, out JsonProperty keyProperty)
    {
        elementType = objectType.GetListType();
        if (elementType == null)
        {
            keyProperty = null;
            return false;
        }
        var contract = contractResolver.ResolveContract(elementType) as JsonObjectContract;
        if (contract == null)
        {
            keyProperty = null;
            return false;
        }
        keyProperty = contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonMergeKeyAttribute), true).Count > 0).SingleOrDefault();
        return keyProperty != null;
    }

    public override bool CanConvert(Type objectType)
    {
        Type elementType;
        JsonProperty keyProperty;
        return CanConvert(contractResolver, objectType, out elementType, out keyProperty);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (contractResolver != serializer.ContractResolver)
            throw new InvalidOperationException("Inconsistent contract resolvers");
        Type elementType;
        JsonProperty keyProperty;
        if (!CanConvert(contractResolver, objectType, out elementType, out keyProperty))
            throw new JsonSerializationException(string.Format("Invalid input type {0}", objectType));

        if (reader.TokenType == JsonToken.Null)
            return existingValue;

        var list = existingValue as IList;
        if (list == null || list.Count == 0)
        {
            list = list ?? (IList)contractResolver.ResolveContract(objectType).DefaultCreator();
            serializer.Populate(reader, list);
        }
        else
        {
            var jArray = JArray.Load(reader);
            var comparer = new KeyedListMergeComparer();
            var lookup = jArray.ToLookup(i => i[keyProperty.PropertyName].ToObject(keyProperty.PropertyType, serializer), comparer);
            var done = new HashSet<JToken>();
            foreach (var item in list)
            {
                var key = keyProperty.ValueProvider.GetValue(item);
                var replacement = lookup[key].Where(v => !done.Contains(v)).FirstOrDefault();
                if (replacement != null)
                {
                    using (var subReader = replacement.CreateReader())
                        serializer.Populate(subReader, item);
                    done.Add(replacement);
                }
            }
            // Populate the NEW items into the list.
            if (done.Count < jArray.Count)
                foreach (var item in jArray.Where(i => !done.Contains(i)))
                {
                    list.Add(item.ToObject(elementType, serializer));
                }
        }
        return list;
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    class KeyedListMergeComparer : IEqualityComparer<object>
    {
        #region IEqualityComparer<object> Members

        bool IEqualityComparer<object>.Equals(object x, object y)
        {
            if (object.ReferenceEquals(x, y))
                return true;
            else if (x == null || y == null)
                return false;
            return x.Equals(y);
        }

        int IEqualityComparer<object>.GetHashCode(object obj)
        {
            if (obj == null)
                return 0;
            return obj.GetHashCode();
        }

        #endregion
    }
}

public static class TypeExtensions
{
    public static Type GetListType(this Type type)
    {
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

注意,转换器需要知道当前正在使用的IContractResolver。拥有它可以更容易地找到关键参数,并确保,如果密钥参数具有[JsonProperty(name)]属性,则替换名称将得到尊重。

然后添加属性:

代码语言:javascript
运行
复制
class Child
{
    [JsonMergeKey]
    [JsonProperty("Uuid")] // Replacement name for testing
    public Guid UUID { get; set; }

    public string Name { get; set; }

    public int Score { get; set; }
}

并按以下方式使用该转换器:

代码语言:javascript
运行
复制
        var serializer = JsonSerializer.CreateDefault();
        var converter = new KeyedListMergeConverter(serializer.ContractResolver);
        serializer.Converters.Add(converter);

        using (var reader = new StringReader(updateJson))
        {
            serializer.Populate(reader, parent);
        }

转换器假定JSON中始终存在密钥参数。此外,如果正在合并的JSON中的任何条目都有在现有列表中找不到的键,则将其追加到列表中。

更新

最初的转换器是专门为List硬编码的,并利用了List<T>同时实现IList<T>IList这一事实。如果您的集合不是List<T>,但仍然实现了IList<T>,那么下面的操作应该是有效的:

代码语言:javascript
运行
复制
public class KeyedIListMergeConverter : JsonConverter
{
    readonly IContractResolver contractResolver;

    public KeyedIListMergeConverter(IContractResolver contractResolver)
    {
        if (contractResolver == null)
            throw new ArgumentNullException("contractResolver");
        this.contractResolver = contractResolver;
    }

    static bool CanConvert(IContractResolver contractResolver, Type objectType, out Type elementType, out JsonProperty keyProperty)
    {
        if (objectType.IsArray)
        {
            // Not implemented for arrays, since they cannot be resized.
            elementType = null;
            keyProperty = null;
            return false;
        }
        var elementTypes = objectType.GetIListItemTypes().ToList();
        if (elementTypes.Count != 1)
        {
            elementType = null;
            keyProperty = null;
            return false;
        }
        elementType = elementTypes[0];
        var contract = contractResolver.ResolveContract(elementType) as JsonObjectContract;
        if (contract == null)
        {
            keyProperty = null;
            return false;
        }
        keyProperty = contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonMergeKeyAttribute), true).Count > 0).SingleOrDefault();
        return keyProperty != null;
    }

    public override bool CanConvert(Type objectType)
    {
        Type elementType;
        JsonProperty keyProperty;
        return CanConvert(contractResolver, objectType, out elementType, out keyProperty);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (contractResolver != serializer.ContractResolver)
            throw new InvalidOperationException("Inconsistent contract resolvers");
        Type elementType;
        JsonProperty keyProperty;
        if (!CanConvert(contractResolver, objectType, out elementType, out keyProperty))
            throw new JsonSerializationException(string.Format("Invalid input type {0}", objectType));

        if (reader.TokenType == JsonToken.Null)
            return existingValue;

        var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
        var genericMethod = method.MakeGenericMethod(new[] { elementType });
        try
        {
            return genericMethod.Invoke(this, new object[] { reader, objectType, existingValue, serializer, keyProperty });
        }
        catch (TargetInvocationException ex)
        {
            // Wrap the TargetInvocationException in a JsonSerializationException
            throw new JsonSerializationException("ReadJsonGeneric<T> error", ex);
        }
    }

    object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer, JsonProperty keyProperty)
    {
        var list = existingValue as IList<T>;
        if (list == null || list.Count == 0)
        {
            list = list ?? (IList<T>)contractResolver.ResolveContract(objectType).DefaultCreator();
            serializer.Populate(reader, list);
        }
        else
        {
            var jArray = JArray.Load(reader);
            var comparer = new KeyedListMergeComparer();
            var lookup = jArray.ToLookup(i => i[keyProperty.PropertyName].ToObject(keyProperty.PropertyType, serializer), comparer);
            var done = new HashSet<JToken>();
            foreach (var item in list)
            {
                var key = keyProperty.ValueProvider.GetValue(item);
                var replacement = lookup[key].Where(v => !done.Contains(v)).FirstOrDefault();
                if (replacement != null)
                {
                    using (var subReader = replacement.CreateReader())
                        serializer.Populate(subReader, item);
                    done.Add(replacement);
                }
            }
            // Populate the NEW items into the list.
            if (done.Count < jArray.Count)
                foreach (var item in jArray.Where(i => !done.Contains(i)))
                {
                    list.Add(item.ToObject<T>(serializer));
                }
        }
        return list;
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    class KeyedListMergeComparer : IEqualityComparer<object>
    {
        #region IEqualityComparer<object> Members

        bool IEqualityComparer<object>.Equals(object x, object y)
        {
            return object.Equals(x, y);
        }

        int IEqualityComparer<object>.GetHashCode(object obj)
        {
            if (obj == null)
                return 0;
            return obj.GetHashCode();
        }

        #endregion
    }
}

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> GetIListItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IList<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }
}

注意,合并不是为数组实现的,因为它们不能调整大小。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34628916

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档