我有一个类似于这个简化示例的JSON输入.
{
"model1": {
"$type": "MyType, MyAssembly",
"A": 5
},
"model2": {
"C": "something"
}
Run Code Online (Sandbox Code Playgroud)
我想实现的是一种"混合"的结果,我指的是顶层ExpandoObject,有两个属性model1和model2,但model1将有一个强类型MyType(基于Json.NET类型的信息.由于model2没有类型信息,它将是一个嵌套ExpandoObject.这个逻辑在更深的嵌套级别也应该是相同的(参见我的更新),在这方面简化了示例.
我的问题是我无法实现"混合".一种方法我可以有一个完全强类型的结果(如果顶级对象是强类型的),另一种方式我可以有一个完全动态的结果(一切都是ExpandoObject,或者第三种方式,我可以拥有一个JObject在这个没有意义的场景.
// this will give a fully dynamic result, regardless the child type information
var result = JsonConvert.DeserializeObject<ExpandoObject>(input, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
Run Code Online (Sandbox Code Playgroud)
UPDATE
我刚刚尝试反序列化为泛型IDictionary,这样我就可以获得顶级子属性的强类型结果,这在技术上解决了我的例子.但是,在较低级别它仍然无法正常工作,并JObject为无类型的子属性提供结果.总的来说,这对我的实际用例来说不是一个好的解决方案.
问题是 Json.NETExpandoObjectConverter根本不处理它自己的任何元数据属性,例如"$type"、"id"或"$ref"。
然而,由于 Json.NET 是开源的,并且其 MIT 许可证允许修改,因此最简单的解决方案可能是制作您自己的副本ExpandoObjectConverter并根据您的需要进行调整,就像Json.NET 反序列化为带有引用的动态对象一样。您还需要复制一些低级 JSON 实用程序:
/// <summary>
/// Converts an ExpandoObject to and from JSON.
/// 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
/// </summary>
public class TypeNameHandlingExpandoObjectConverter : JsonConverter
{
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// can write is set to false
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return ReadValue(reader, serializer);
}
private object ReadValue(JsonReader reader, JsonSerializer serializer)
{
if (!reader.MoveToContent())
{
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
switch (reader.TokenType)
{
case JsonToken.StartObject:
return ReadObject(reader, serializer);
case JsonToken.StartArray:
return ReadList(reader, serializer);
default:
if (JsonTokenUtils.IsPrimitiveToken(reader.TokenType))
{
return reader.Value;
}
throw JsonSerializationExceptionHelper.Create(reader, string.Format("Unexpected token when converting ExpandoObject: {0}", reader.TokenType));
}
}
private object ReadList(JsonReader reader, JsonSerializer serializer)
{
IList<object> list = new List<object>();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
default:
object v = ReadValue(reader, serializer);
list.Add(v);
break;
case JsonToken.EndArray:
return list;
}
}
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
private object ReadObject(JsonReader reader, JsonSerializer serializer)
{
if (serializer.TypeNameHandling != TypeNameHandling.None)
{
var obj = JObject.Load(reader);
Type polymorphicType = null;
var polymorphicTypeString = (string)obj["$type"];
if (polymorphicTypeString != null)
{
if (serializer.TypeNameHandling != TypeNameHandling.None)
{
string typeName, assemblyName;
ReflectionUtils.SplitFullyQualifiedTypeName(polymorphicTypeString, out typeName, out assemblyName);
polymorphicType = serializer.Binder.BindToType(assemblyName, typeName);
}
obj.Remove("$type");
}
if (polymorphicType == null || polymorphicType == typeof(ExpandoObject))
{
using (var subReader = obj.CreateReader())
return ReadExpandoObject(subReader, serializer);
}
else
{
using (var subReader = obj.CreateReader())
return serializer.Deserialize(subReader, polymorphicType);
}
}
else
{
return ReadExpandoObject(reader, serializer);
}
}
private object ReadExpandoObject(JsonReader reader, JsonSerializer serializer)
{
IDictionary<string, object> expandoObject = new ExpandoObject();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
string propertyName = reader.Value.ToString();
if (!reader.Read())
{
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
object v = ReadValue(reader, serializer);
expandoObject[propertyName] = v;
break;
case JsonToken.Comment:
break;
case JsonToken.EndObject:
return expandoObject;
}
}
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ExpandoObject));
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite
{
get { return false; }
}
}
internal static class JsonTokenUtils
{
// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/JsonTokenUtils.cs
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;
}
}
}
internal static class JsonReaderExtensions
{
// Adapted from internal bool JsonReader.MoveToContent()
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonReader.cs#L1145
public static bool MoveToContent(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
JsonToken t = reader.TokenType;
while (t == JsonToken.None || t == JsonToken.Comment)
{
if (!reader.Read())
{
return false;
}
t = reader.TokenType;
}
return true;
}
}
internal static class JsonSerializationExceptionHelper
{
public static JsonSerializationException Create(this JsonReader reader, string format, params object[] args)
{
// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs
var lineInfo = reader as IJsonLineInfo;
var path = (reader == null ? null : reader.Path);
var message = string.Format(CultureInfo.InvariantCulture, format, args);
if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
{
message = message.Trim();
if (!message.EndsWith(".", StringComparison.Ordinal))
message += ".";
message += " ";
}
message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
if (lineInfo != null && lineInfo.HasLineInfo())
message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
message += ".";
return new JsonSerializationException(message);
}
}
internal static class ReflectionUtils
{
// Utilities taken from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs
// I couldn't find a way to access these directly.
public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName)
{
int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName);
if (assemblyDelimiterIndex != null)
{
typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim();
assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim();
}
else
{
typeName = fullyQualifiedTypeName;
assemblyName = null;
}
}
private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName)
{
int scope = 0;
for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
{
char current = fullyQualifiedTypeName[i];
switch (current)
{
case '[':
scope++;
break;
case ']':
scope--;
break;
case ',':
if (scope == 0)
{
return i;
}
break;
}
}
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
然后像这样使用它:
var settings = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
TypeNameHandling = TypeNameHandling.Auto,
Converters = new [] { new TypeNameHandlingExpandoObjectConverter() },
};
var expando2 = JsonConvert.DeserializeObject<ExpandoObject>(input, settings);
Run Code Online (Sandbox Code Playgroud)
原型小提琴。
最后,在使用时TypeNameHandling,请注意Newtonsoft 文档中的这一警告:
当您的应用程序从外部源反序列化 JSON 时,应谨慎使用 TypeNameHandling。当使用 None 以外的值进行反序列化时,应使用自定义 SerializationBinder 验证传入类型。
有关为什么这可能是必要的讨论,请参阅Newtonsoft Json 中的 TypeNameHandling 警告。
| 归档时间: |
|
| 查看次数: |
2446 次 |
| 最近记录: |