我有一个类似于这个简化示例的JSON输入。
{
"model1": {
"$type": "MyType, MyAssembly",
"A": 5
},
"model2": {
"C": "something"
}
我想要实现的是" hybrid"结果,我的意思是顶级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 });
更新
我刚刚尝试反序列化为通用IDictionary
,这样我就能获得顶级子属性的强类型结果,这在技术上解决了我的例子。但是,在较低级别它仍然无法正常工作,并为无类型的子属性提供JObject
结果。总的来说,它不是我真实用例的好解决方案。
答案 0 :(得分:3)
问题是Json.NET的ExpandoObjectConverter
根本不处理任何自己的元数据属性,例如"$type"
,"id"
或"$ref"
。
但是,由于Json.NET是开放源代码及其MIT许可证allows modification,因此最简单的解决方案可能是制作您自己的ExpandoObjectConverter
副本,并根据您的需求调整它{3}}。您还需要复制一些低级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;
}
}
然后使用它:
var settings = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
TypeNameHandling = TypeNameHandling.Auto,
Converters = new [] { new TypeNameHandlingExpandoObjectConverter() },
};
var expando2 = JsonConvert.DeserializeObject<ExpandoObject>(input, settings);
原型Json.NET Deserialization into dynamic object with referencing。
最后,在使用TypeNameHandling
时,请注意fiddle中的这一注意事项:
当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。使用非None以外的值进行反序列化时,应使用自定义SerializationBinder验证传入类型。
有关可能需要执行此操作的讨论,请参阅 Newtonsoft docs 。
答案 1 :(得分:0)
这是我将如何做到的:
void Main()
{
var json = "{\r\n \"model1\": {\r\n \"$type\": \"MyType, MyAssembly\",\r\n \"A\": 5\r\n },\r\n \"model2" +
"\": {\r\n \"C\": \"something\"\r\n}}";
var result = JsonConvert.DeserializeObject<Result>(json);
}
public class Result
{
public MyType Model1 { get; set; }
public ExpandoObject Model2 { get; set;}
}
public class MyType { public int A { get; set;} }
您还可以为Result.Model2
提供dynamic
类型(允许您使用result.Model2.something
等语法访问其属性)或JSON.NET&#39; {{1} },这是更面向JSON的。
但是,如果你说你不想要JObject
这样的课程,但你希望JSON的Result
能够确定具体的实例类型,您可以使用TypeNameHandling
setting。
$type
请注意,如果您允许客户端提供的JSON值在.NET环境中实例化任意类型,则会产生安全隐患。
答案 2 :(得分:0)
有点旧的线程,但是这是可以做的。
首先构造您的ExpandoObject。例如:
dynamic someObject = new ExpandoObject();
someObject.Name = "My Expando Object";
someObject.SomeProperty = 123;
然后我建议使用“ JsonConvert”来序列化此对象(我知道您想反过来做,但是请耐心等待。)
因此,让我们按照以下顺序将“ someObject”序列化为一个test.json文件:
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All // Or Use .Auto for light weigth output
};
File.WriteAllText(@"e:\test.json", JsonConvert.SerializeObject(someObject, settings));
现在,如果您打开生成的Json文件,您将能够看到此JsonConverter所需的确切语法。现在,您可以编写自己的Json并执行逆运算。即。将您自己的Json文件反序列化为动态对象,如下所示:
dynamic test = JsonConvert.DeserializeObject<ExpandoObject>(File.ReadAllText(@"e:\yourOwnJsonFile.json"), settings);
最后,为了访问对象的属性,请执行以下操作:
((dynamic)test.Name = "My Own Expando Object";