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.
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:
- 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.
- The elements in the array can be Json objects, in which case I have to deserialize them into
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();
}
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
.
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?
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;
}
}
}
Then use it like:
protected IQueryable<dynamic> TestMethod(string r)
{
return JsonExtensions.ReadJsonAsDynamicQueryable(r);
}
Or, you could call ReadJsonAsDynamicQueryable
from within the ReadJson()
method of a custom JsonConverter
that you create.
Sample fiddle.
来源:https://stackoverflow.com/questions/43214424/converting-arbitrary-json-response-to-list-of-things