首页 > 解决方案 > JSON反序列化映射数组到自己的属性

问题描述

考虑以下第 3 方 (Magento) API 响应,其中这是产品集合中的一个条目:

{
   "id":121,
   "sku":"008MBLU",
   "name":"Pillow Covers - King Mauve (2-pack)",
   "custom_attributes":{
      "11":{
         "attribute_code":"ship_length",
         "value":"11.0000"
      },
      "16":{
         "attribute_code":"ship_width",
         "value":"7.0000"
      },
      "19":{
         "attribute_code":"ship_height",
         "value":"1.0000"
      }
   }
}

并将所需的结果类反序列化为:

public class Product
{
    [JsonProperty("id")]
    public long Id { get; set; }

    [JsonProperty("sku")]
    public string SKU { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("ship_length")]
    public decimal ShipLength { get; set; }

    [JsonProperty("ship_width")]
    public decimal ShipWidth { get; set; }

    [JsonProperty("ship_height")]
    public decimal ShipHeight { get; set; }
}

我发现了这篇文章,这是我需要的部分内容,它将忽略int包装每个custom_attribute. 但是我不知道从哪里开始使用针对该custom_attribute属性的自定义解析器,然后将其值分配给另一个属性……而且我通常是自定义解析器的新手。

标签: jsonjson.netdeserialization

解决方案


在读取对应于 a 的 JSON 时Product,您需要将 "custom_attributes"属性重构为顶级 JSON 对象,在该对象中它们可以被序列化程序识别。这可以通过将 JSON 预加载到层次结构中的自定义JsonConverterJToken来轻松完成,然后重新构建层次结构:

public class CustomAttributeObjectConverter<T> : JsonConverter<T> where T : new()
{
    public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var obj = JToken.Load(reader).ToJTokenType<JObject>();
        if (obj == null)
            return (T)(object)null;
        var attributes = obj["custom_attributes"].RemoveFromLowestPossibleParent().ToJTokenType<JObject>();
        if (attributes != null)
        {
            foreach (var item in attributes.PropertyValues().OfType<JObject>())
            {
                var name = (string)item["attribute_code"];
                if (name != null)
                    obj.Add(name, item["value"]);
            }
        }
        if (!hasExistingValue)
            existingValue = (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        using (var tokenReader = obj.CreateReader())
            serializer.Populate(tokenReader, existingValue);
        return existingValue;
    }

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

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

public static partial class JsonExtensions
{
    public static TJToken ToJTokenType<TJToken>(this JToken item) where TJToken : JToken
    {
        var result = item as TJToken;
        if (item != null)
            return result;
        if (item == null || item.Type == JTokenType.Null)
            return null;
        throw new JsonException(string.Format("Cannot cast {0} to {1}", item.Type, typeof(TJToken)));
    }

    public static JToken RemoveFromLowestPossibleParent(this JToken node)
    {
        if (node == null)
            return null;
        // If the parent is a JProperty, remove that instead of the token itself.
        var contained = node.Parent is JProperty ? node.Parent : node;
        contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (contained is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }
}

然后反序列化如下:

var settings = new JsonSerializerSettings
{
    Converters = { new CustomAttributeObjectConverter<Product>() },
};

var product = JsonConvert.DeserializeObject<Product>(json, settings);

笔记:

  • 我没有尝试实现WriteJson,因为在序列化时无法以通用方式区分哪些属性应该降级为"custom_attributes"对象。

    如果这是一个要求,您可以实现一些自定义属性来标记适当的属性。

演示小提琴在这里


推荐阅读