将任意json响应转换为“事物"列表.

问题描述:

我遇到了一个不寻常的问题.这可能不是一个很现实的情况,但这就是我所要学习的内容,所以请多多包涵.

I'm having an unusual problem. It might not be a very realistic scenario, but this is what I have gotten myself into, so please bear with me.

我有一个返回Json的API,并且我正在使用Json.NET处理Json响应.问题在于API可以返回很多东西,而且我必须能够通过以下方式反序列化响应:

I have an API that returns Json and I'm using Json.NET to process the Json response. The problem is that the API can return a number of things and I have to be able to deserialize the response the following way:

  • API可以返回单个Json对象.在这种情况下,我必须将其反序列化为ExpandoObject并将其放入List<dynamic>.
  • API可以返回null和undefined等,在这种情况下,我必须返回一个空列表.
  • API可以返回单个原始值,例如Json字符串或Json浮点数.在这种情况下,我必须将其反序列化为适当的.NET类型,将其放入List<dynamic>并返回.
  • API可以返回Json数组.在这种情况下,我必须将数组反序列化为List<dynamic>:
    • 数组中的元素可以是Json对象,在这种情况下,我必须再次将它们反序列化为ExpandoObject,然后将它们放入列表中.
    • 元素也可以是原始值.在这种情况下,我必须将它们反序列化为正确的.NET类型,并将它们放入列表中.
    • The API can return a single Json object. In this case I have to deserialize it into an ExpandoObject and put it into a List<dynamic>.
    • The API can return null and undefined and the alike, in which case I have to return an empty list.
    • The API can return a single primitive value, like a Json string or a Json float. In this case I have to deserialize it into the appropriate .NET type, put it in a List<dynamic> and return that.
    • The API can return a Json array. In this case I have to deserialize the array into a List<dynamic>:
      • The elements in the array can be Json objects, in which case I have to deserialize them into ExpandoObject again, and put them in the list.
      • The elements can also be primitive values. In this case I have to deserialize them into the proper .NET type and put them in the list.

      根据我的研究,到目前为止,我得出的结论是:

      Based on my research, here's what I have come up so far:

      protected IQueryable<dynamic> TestMethod(string r)
      {
        using (StringReader sr = new StringReader(r))
        using (JsonTextReader reader = new JsonTextReader(sr))
        {
          if (!reader.Read())
          {
            return new List<ExpandoObject>().AsQueryable();
          }
      
          switch (reader.TokenType)
          {
            case JsonToken.None:
            case JsonToken.Null:
            case JsonToken.Undefined:
               return new List<ExpandoObject>().AsQueryable();
            case JsonToken.StartArray:
               return JsonConvert.DeserializeObject<List<ExpandoObject>>(r).AsQueryable();
            case JsonToken.StartObject:
               return DeserializeAs<ExpandoObject>(r);
            case JsonToken.Integer:
               return DeserializeAs<long>(r);
            case JsonToken.Float:
               return DeserializeAs<double>(r);
            // other Json primitives deserialized here
            case JsonToken.StartConstructor:
            // listing other not processed tokens
            default:
               throw new InvalidOperationException($"Token {reader.TokenType} cannot be the first token in the result");
          }
        }
      }
      
      private IQueryable<dynamic> DeserializeAs<T>(string r)
      {
         T instance = JsonConvert.DeserializeObject<T>(r);
         return new List<dynamic>() { instance }.AsQueryable();
      }
      

      问题出在最后一个要点.在切换情况下,当反序列化器遇到StartArray令牌时,它将尝试将json反序列化为List<ExpandoObject>,但是如果数组包含整数,则无法将它们反序列化为ExpandoObject.

      The problem is with the last bullet point. In the switch-case, when the deserializer encounters StartArray token, it tries to deserialize the json into a List<ExpandoObject>, but if the array contains integers, they cannot be deserialized into ExpandoObject.

      谁能给我一个简单的解决方案来支持这两种情况:List<ExpandoObject>的Json对象数组和各自列表的Json原语数组?

      Can anyone give me a simple solution to support both scenarios: array of Json objects to List<ExpandoObject> and array of Json primitives to their respective list?

由于Json.NET已获得 ExpandoObjectConverter 来满足您的需求,并创建以下方法:

Since Json.NET is licensed under the MIT license, you could adapt the logic of ExpandoObjectConverter to fit your needs, and create the following method:

public static class JsonExtensions
{
    public static IQueryable<object> ReadJsonAsDynamicQueryable(string json, JsonSerializerSettings settings = null)
    {
        var serializer = JsonSerializer.CreateDefault(settings);
        using (StringReader sr = new StringReader(json))
        using (JsonTextReader reader = new JsonTextReader(sr))
        {
            var root = JsonExtensions.ReadJsonAsDynamicQueryable(reader, serializer);
            return root;
        }
    }

    public static IQueryable<dynamic> ReadJsonAsDynamicQueryable(JsonReader reader, JsonSerializer serializer)
    {
        dynamic obj;

        if (!TryReadJsonAsDynamic(reader, serializer, out obj) || obj == null)
            return Enumerable.Empty<dynamic>().AsQueryable();

        var list = obj as IList<dynamic> ?? new [] { obj };

        return list.AsQueryable();
    }

    public static bool TryReadJsonAsDynamic(JsonReader reader, JsonSerializer serializer, out dynamic obj)
    {
        // Adapted from:
        // https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/ExpandoObjectConverter.cs
        // License:
        // https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md

        if (reader.TokenType == JsonToken.None)
            if (!reader.Read())
            {
                obj = null;
                return false;
            }

        switch (reader.TokenType)
        {
            case JsonToken.StartArray:
                var list = new List<dynamic>();
                ReadList(reader,
                    (r) =>
                    {
                        dynamic item;
                        if (TryReadJsonAsDynamic(reader, serializer, out item))
                            list.Add(item);
                    });
                obj = list;
                return true;

            case JsonToken.StartObject:
                obj = serializer.Deserialize<ExpandoObject>(reader);
                return true;

            default:
                if (reader.TokenType.IsPrimitiveToken())
                {
                    obj = reader.Value;
                    return true;
                }
                else
                {
                    throw new JsonSerializationException("Unknown token: " + reader.TokenType);
                }
        }
    }

    static void ReadList(this JsonReader reader, Action<JsonReader> readValue)
    {
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                default:
                    readValue(reader);
                    break;
                case JsonToken.EndArray:
                    return;
            }
        }
        throw new JsonSerializationException("Unexpected end when reading List.");
    }

    public static bool IsPrimitiveToken(this JsonToken token)
    {
        switch (token)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return true;
            default:
                return false;
        }
    }
}

然后像这样使用它:

protected IQueryable<dynamic> TestMethod(string r)
{
    return JsonExtensions.ReadJsonAsDynamicQueryable(r);
}

或者,您可以从 ReadJson() 创建的自定义JsonConverter 的方法.

Or, you could call ReadJsonAsDynamicQueryable from within the ReadJson() method of a custom JsonConverter that you create.

示例小提琴.