JSON десериализовать отображающий массив в собственное свойство - PullRequest
1 голос
/ 21 октября 2019

Рассмотрим следующий ответ API стороннего (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, а затем присвоить его значение другому свойству .... и я новичок в настраиваемых преобразователях в целом.

1 Ответ

1 голос
/ 21 октября 2019

При чтении JSON, соответствующего Product, необходимо реструктурировать свойства "custom_attributes" до объекта JSON верхнего уровня, где они могут быть распознаны сериализатором. Это проще всего сделать с помощью custom JsonConverter, который предварительно загружает JSON в иерархию JToken, а затем реструктурирует иерархию:

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" при сериализации.

    Если это требование, вы можете реализовать некоторый пользовательский атрибут , чтобы пометитьсоответствующие свойства.

Демонстрационная скрипка здесь .

...