使用键将嵌套的JObject反序列化为List

时间:2018-05-09 16:50:06

标签: c# json.net

我需要使用Newtonsoft.Json将json反序列化回对象实例。

但是,它是一个列表类型对象,该条目的键对我很有用。

我不知道如何在不手动逐个映射字段的情况下自动反序列化。

以下是回复:

internal class WhatToMineCalculatorsResponse
{
    // Should be Dictionary???
    [JsonProperty("coins")]
    public IList<WhatToMineCalculatorResponse> Coins { get; set; }
}

internal class WhatToMineCalculatorResponse
{
    // I want the key set in this field
    public string Name { get; set; }

    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonProperty("tag")]
    public string Symbol { get; set; }

    [JsonProperty("status")]
    public string Status { get; set; }

    [JsonProperty("algorithm")]
    public string Algo { get; set; }

    [JsonProperty("listed")]
    public bool IsListed { get; set; }
}

完整回复:https://whattomine.com/calculators.json

我对班级的最佳猜测是:

{{1}}

请注意,我希望我的课程中包含该键,但不是字典的键。以后很难找到密钥。

1 个答案:

答案 0 :(得分:1)

您不能完全通过属性指定某些IList<T>的{​​{1}}应序列化为JSON对象。正如其Serialization Guide中所解释的,Newtonsoft将字典和散列表映射到JSON对象,但将所有其他可枚举,列表和数组映射到JSON数组。相反,您必须使用custom JsonConverter

首先,定义以下转换器:

T

然后,您可以按如下方式反序列化:

internal class WhatToMineCalculatorResponseListConverter : KeyedListToJsonObjectConverterBase<WhatToMineCalculatorResponse>
{
    protected override string KeyPropertyUnderlyingName => nameof(WhatToMineCalculatorResponse.Name);
}

public abstract class KeyedListToJsonObjectConverterBase<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        if (objectType.IsArray)
            return false;
        return typeof(IList<T>).IsAssignableFrom(objectType);
    }

    protected abstract string KeyPropertyUnderlyingName { get; }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Get the key property name from the underlying name
        var itemContract = serializer.ContractResolver.ResolveContract(typeof(T)) as JsonObjectContract;
        if (itemContract == null)
            throw new JsonSerializationException(string.Format("type {0} is not serialized as a JSON object"));
        var keyProperty = itemContract.Properties.Where(p => p.UnderlyingName == KeyPropertyUnderlyingName).SingleOrDefault();
        if (keyProperty == null)
            throw new JsonSerializationException(string.Format("Key property {0} not found", KeyPropertyUnderlyingName));

        // Validate initial token.
        if (reader.SkipComments().TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType != JsonToken.StartObject)
            throw new JsonSerializationException(string.Format("Unexpected token {0} at {1}", reader.TokenType, reader.Path));

        // Allocate the List<T>.  (It might be some subclass of List<T>, so use the default creator.
        var list = existingValue as ICollection<T> ?? (ICollection<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();

        // Process each key/value pair.
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                case JsonToken.EndObject:
                    return list;
                case JsonToken.PropertyName:
                    {
                        // Get the name.
                        var name = (string)reader.Value;
                        reader.ReadAndAssert();
                        // Load the object
                        var jItem = JObject.Load(reader);
                        // Add the name property
                        jItem.Add(keyProperty.PropertyName, name);
                        // Deserialize the item and add it to the list.
                        list.Add(jItem.ToObject<T>(serializer));
                    }
                    break;
                default:
                    {
                        throw new JsonSerializationException(string.Format("Unexpected token {0} at {1}", reader.TokenType, reader.Path));
                    }
            }
        }
        // Should not come here.
        throw new JsonSerializationException("Unclosed object at path: " + reader.Path);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // Get the key property name from the underlying name
        var itemContract = serializer.ContractResolver.ResolveContract(typeof(T)) as JsonObjectContract;
        if (itemContract == null)
            throw new JsonSerializationException(string.Format("type {0} is not serialized as a JSON object"));
        var keyProperty = itemContract.Properties.Where(p => p.UnderlyingName == KeyPropertyUnderlyingName).SingleOrDefault();
        if (keyProperty == null)
            throw new JsonSerializationException(string.Format("Key property {0} not found", KeyPropertyUnderlyingName));

        var converters = serializer.Converters.ToArray();
        var list = (IEnumerable<T>)value;
        writer.WriteStartObject();
        foreach (var item in list)
        {
            var jItem = JObject.FromObject(item, serializer);
            var name = (string)jItem[keyProperty.PropertyName];
            jItem.Remove(keyProperty.PropertyName);
            writer.WritePropertyName(name);
            jItem.WriteTo(writer, converters);
        }
        writer.WriteEndObject();
    }
}

public static partial class JsonExtensions
{
    public static JsonReader SkipComments(this JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    }

    public static void ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
        {
            new JsonReaderException(string.Format("Unexpected end at path {0}", reader.Path));
        }
    }
}

注意:

  • 在序列化var settings = new JsonSerializerSettings { Converters = { new WhatToMineCalculatorResponseListConverter() }, }; var root = JsonConvert.DeserializeObject<WhatToMineCalculatorsResponse>(responseString, settings); 且类型KeyedListToJsonObjectConverterBase<T>具有要用作JSON的特定属性的任何情况下,都可以重用基类转换器List<T>对象属性名称。只需覆盖T并返回实际的.Net属性名称(不是序列化名称)。

  • 代码看起来有点复杂,因为我使KeyPropertyUnderlyingName足够通用,可以处理密钥属性为只读的情况,例如:

    KeyedListToJsonObjectConverterBase<T>

工作.Net小提琴here