c# - 将任意 json 响应转换为 "things"列表

标签 c# .net json json.net

我遇到了一个不寻常的问题。这可能不是一个非常现实的场景,但这就是我自己陷入的情况,所以请耐心等待。

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

  • API 可以返回单个 Json 对象。在这种情况下,我必须将其反序列化为 ExpandoObject并将其放入 List<dynamic> .
  • API 可以返回 null 和 undefined 等,在这种情况下我必须返回一个空列表。
  • API 可以返回单个原始值,例如 Json 字符串或 Json float 。在这种情况下,我必须将其反序列化为适当的 .NET 类型,将其放入 List<dynamic> 中。并返回。
  • API 可以返回 Json 数组。在这种情况下,我必须将数组反序列化为 List<dynamic> :
    • 数组中的元素可以是 Json 对象,在这种情况下我必须将它们反序列化为 ExpandoObject再次,并将它们放入列表中。
    • 元素也可以是原始值。在这种情况下,我必须将它们反序列化为正确的 .NET 类型并将它们放入列表中。

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

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();
}

问题出在最后一个要点上。在 switch-case 中,当解串器遇到 StartArray 时 token ,它尝试将 json 反序列化为 List<ExpandoObject> ,但如果数组包含整数,则无法将它们反序列化为 ExpandoObject .

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

最佳答案

由于 Json.NET 已根据 MIT license 获得许可,您可以调整 ExpandoObjectConverter 的逻辑以满足您的需求,并创建以下方法:

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() 中调用 ReadJsonAsDynamicQueryable方法custom JsonConverter您创建的。

样本fiddle .

关于c# - 将任意 json 响应转换为 "things"列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43214424/

相关文章:

c# - 如何将 Outlook 中的用户邮件签名附加到以编程方式创建的电子邮件

c# - 带有图像和标签的 MVC Razor 下拉列表?

c# - StructureMap ObjectFactory.Reset 内存泄漏?

.net - WCF:如何从配置中获取 Binding 对象

php - 将连接数据从 mysql 输出到 json 作为 json 对象

android json多维数组

C# 访问方法重定向

c# - .NET 核心 2.2。尝试删除时如何摆脱此 HTTP 405 错误?

.net - VS 2005 可以为 .NET 4 构建 C# 吗?

javascript - 关联数组值到json