将任意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 aList<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 aList<ExpandoObject>
, but if the array contains integers, they cannot be deserialized intoExpandoObject
.谁能给我一个简单的解决方案来支持这两种情况:
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? - The elements in the array can be Json objects, in which case I have to deserialize them into
- 数组中的元素可以是Json对象,在这种情况下,我必须再次将它们反序列化为
由于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.
示例小提琴.