JsonConverter:根据入站JSON返回单个对象或List <object>

时间:2016-04-05 14:48:34

标签: c# serialization json.net

当我的API发送回单个对象时,我们将其称为Item(OData术语中的GetEntity),它看起来像这样:

{
  "d" : {
    "Item" : "123456",
    "OldItem" : "78921",
  }
}

当我抓住一组相同的对象,即返回Item of Item时,我得到:

{  
   "d":{  
      "results":[  
         {  
            "Item":"343431",
            "OldItem":"21314"
         },
         {  
            "Item":"341321",
            "OldItem":"43563"
         }
      ]
   }
}

除了显而易见的&#34; d&#34;基本节点我需要摆脱,我在尝试使用C#中的同一个类时遇到了麻烦。我有一个Item类:

public class Material : IEntity
    {
        [JsonProperty("Item")]
        public string material_number { get; set; }
        [JsonProperty("OldItem")]
        public string old_material_number { get; set; }

        // Methods
        public Material() {}

        public bool Validate() { throw new NotImplementedException(); }
    }

我希望能够调用自定义JsonConverter来处理这个问题,但我还没有能够获得一些示例,可以让单个对象,数组转换器工作。理想情况下,我应该可以致电:

JsonConvert.DeserializeObject<T>其中TMaterialList<Material>。如何构建JsonConverter来处理这两种情况?

我正在调用JsonConvert.DeserializeObject<T>

if (response.IsSuccessStatusCode)
            {
                return JsonConvert.DeserializeObject<T>(JObject.Parse(response.Content.ReadAsStringAsync().Result).SelectToken("d").ToString());
            }
            else
            {
                throw new Exception("Service Error");
            }

2 个答案:

答案 0 :(得分:1)

这是JsonConverter,适用于您的情况。请注意,如果您可以收到任何其他未在您的问题中显示的JSON格式 - 例如,如果d在没有结果时可以具有值null - 您可能需要调整转换器。目前,如果遇到它不期望的内容,它会抛出异常,但如果您愿意,可以让它返回null或者返回一个空列表。

public class MaterialArrayConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Object)
        {
            JToken results = token["results"];
            if (results != null && results.Type == JTokenType.Array)
            {
                // we've got multiple items; deserialize to a list
                return results.ToObject<List<Material>>(serializer);
            }
            else if (results == null)
            {
                // "results" property not present; return a list of one item
                return new List<Material> { token.ToObject<Material>(serializer) };
            }
        }
        // some other format we're not expecting
        throw new JsonSerializationException("Unexpected JSON format encountered in MaterialArrayConverter: " + token.ToString());
    }

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

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

    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when [JsonConverter] attribute is used
        return false;
    }
}

您可以通过如下所示注释RootObject类,然后将JSON反序列化为:

来使用它。
public class RootObject
{
    [JsonProperty("d")]
    [JsonConverter(typeof(MaterialArrayConverter))]
    public List<Material> Materials { get; set; }
}

然后:

var root = JsonConvert.DeserializeObject<RootObject>(json);

从那里,您可以检索材料列表并根据需要使用它。

小提琴:https://dotnetfiddle.net/tKb6Ke

答案 1 :(得分:0)

假设您要在集合中退回材料,可以使用以下通用JsonConverter

public class SingleOrResultListConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(ICollection<T>).IsAssignableFrom(objectType);
    }

    const string Results = "results";

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        if (objectType.IsArray)
        {
            var list = (List<T>)ReadJson(reader, typeof(List<T>), new List<T>(), serializer);
            return list.ToArray();
        }
        else
        {
            var list = (ICollection<T>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
            if (reader.TokenType == JsonToken.StartArray)
            {
                serializer.Populate(reader, list);
            }
            else if (reader.TokenType == JsonToken.StartObject)
            {
                JObject obj = null;
                while (reader.Read())
                {
                    switch (reader.TokenType)
                    {
                        case JsonToken.PropertyName:
                            string propertyName = reader.Value.ToString();
                            if (!reader.Read())
                            {
                                throw new JsonSerializationException("Unexpected end while reading collection");
                            }
                            if (propertyName == Results)
                            {
                                serializer.Populate(reader, list);
                            }
                            else
                            {
                                obj = obj ?? new JObject();
                                obj[propertyName] = JToken.Load(reader);
                            }
                            break;
                        case JsonToken.Comment:
                            break;
                        case JsonToken.EndObject:
                            if (obj != null)
                                list.Add(obj.ToObject<T>(serializer));
                            return list;
                    }
                }
                throw new JsonSerializationException("Unexpected end while reading collection");
            }
            else
            {
                throw new JsonSerializationException("Unexpected start token: " + reader.TokenType);
            }
            return list;
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var collection = (ICollection<T>)value;
        if (collection.Count == 1)
        {
            serializer.Serialize(writer, collection.First());
        }
        else
        {
            writer.WriteStartObject();
            writer.WritePropertyName(Results);
            writer.WriteStartArray();
            foreach (var item in collection)
            {
                serializer.Serialize(writer, item);
            }
            writer.WriteEndArray();
            writer.WriteEndObject();
        }
    }
}

然后按如下方式使用:

var obj = JObject.Parse(json);
var subObj = obj["d"] ?? obj; // Strip the "d".

var settings = new JsonSerializerSettings { Converters = new[] { new SingleOrResultListConverter<Material>() } };

var list = subObj.ToObject<List<Material>>(JsonSerializer.CreateDefault(settings));

或者如果您更喜欢数组到List<T>

var array = subObj.ToObject<Material[]>(JsonSerializer.CreateDefault(settings));

原型fiddle

<强>更新

您需要确保分配并将转换器传递给Json.NET。由于您的反序列化方法是通用的,因此您需要执行通用的操作,如下所示:

var json = response.Content.ReadAsStringAsync().Result;
var converters = typeof(T).GetCollectionItemTypes()
    .Select(t => (JsonConverter)Activator.CreateInstance(typeof(SingleOrResultListConverter<>).MakeGenericType(new [] { t })))
    .ToArray();
var settings = new JsonSerializerSettings { Converters = converters };
return JToken.Parse(json).SelectToken("d").ToObject<T>(JsonSerializer.CreateDefault(settings));

使用扩展方法:

public static class TypeExtensions
{
    /// <summary>
    /// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    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> GetCollectionItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }
}