.NET在反序列化JSON时无法分析嵌套对象
我知道有很多关于这种情况的文章,但是我查过的所有答案都不适合我,我认为我的情况有点不同。.NET在反序列化JSON时无法分析嵌套对象
我称为的PropertyValue类,它是一个描述属性值的元数据类,然后也有包含实际值的属性:
public sealed class PropertyValue
{
public PropertyValue()
{
}
public string PropertyName { get; set; }
public string CategoryName { get; }
public string DisplayName { get; }
public int PropertyId { get; }
public string TypeName { get; set;}
public string ToolTip { get; set;}
public string Description { get; }
public object CurrentValue { get; set; }
}
TypeName属性实际上说的是什么类型的对象CurrentValue应该是,并且这些值的范围从System.Int32到我们公司构建的专有对象。问题是,当我尝试使用JsonConvert.DeserializeObject(property)时,它将反序列化除CurrentValue属性以外的所有内容。我尝试在构造函数中使用switch语句来处理所有我们处理的类型并创建该类的新实例,但这并不能解决JSON中的嵌套值。
任何想法?
编辑:我包括我的JSON显示了我们的时区的一类:
{
"PropertyName":"TimeZone",
"CategoryName":"TBD",
"DisplayName":"TimeZone",
"PropertyId":15,
"TypeName":"Namespace.TimeZoneReference",
"ToolTip":"",
"Description":"",
"CurrentValue":{
"timeZoneID":21,
"timeZoneName":"Eastern Standard Time"
}
}
这听起来像你试图重新发明Json.NET的TypeNameHandling
setting。由于您没有使用此设置,而是自行序列化CurrentValue
的类型名称,因此您需要创建custom JsonConverter
以填充PropertyValue
并将CurrentValue
反序列化为所需的类型。否则,Json.NET会将当前值反序列化为基元(如long
或string
)或LINQ to JSON对象,例如JObject
以获取非原始JSON值。 (后者是与两个或三个键/值对你提到看到在评论缠花括号字符串值。)
这里是一个可能的转换器应用到你的类型:
[JsonConverter(typeof(PropertyValueConverter))]
public sealed class PropertyValue
{
public PropertyValue(object CurrentValue)
{
SetCurrentValue(CurrentValue);
}
public PropertyValue()
{
}
public string PropertyName { get; set; }
public string CategoryName { get; set; }
public string DisplayName { get; set; }
public int PropertyId { get; set; }
public string TypeName { get; set; }
public string ToolTip { get; set; }
public string Description { get; set; }
public object CurrentValue { get; set; }
public void SetCurrentValue(object value)
{
CurrentValue = value;
if (value == null)
TypeName = null;
else
TypeName = value.GetType().AssemblyQualifiedName;
}
}
public class PropertyValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(PropertyValue).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var propertyValue = (existingValue as PropertyValue ?? new PropertyValue());
var obj = JObject.Load(reader);
// Remove the CurrentValue property for manual deserialization, and deserialize
var jValue = obj.GetValue("CurrentValue", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();
// Load the remainder of the properties
serializer.Populate(obj.CreateReader(), propertyValue);
// Convert the type name to a type.
// Use the serialization binder to sanitize the input type! See
// https://*.com/questions/39565954/typenamehandling-caution-in-newtonsoft-json
if (!string.IsNullOrEmpty(propertyValue.TypeName) && jValue != null)
{
string typeName, assemblyName;
ReflectionUtils.SplitFullyQualifiedTypeName(propertyValue.TypeName, out typeName, out assemblyName);
var type = serializer.Binder.BindToType(assemblyName, typeName);
if (type != null)
propertyValue.SetCurrentValue(jValue.ToObject(type, serializer));
}
return propertyValue;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}
public 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;
}
}
样品fiddle。 (我不得不做出一些你的属性的读/写,因为他们只读,但不是在构造函数中设置。)
或者,你可以标记您CurrentValue
与[JsonProperty(TypeNameHandling = TypeNameHandling.All)]
:
public sealed class PropertyValue
{
[JsonProperty(TypeNameHandling = TypeNameHandling.All)]
public object CurrentValue { get; set; }
// Remainder as before
做如此,Json.NET将输出"$type"
属性,来指示的实际类型的CurrentObject
,并自动反序列化期间使用类型,例如:
{
"CurrentValue": {
"$type": "Question42537050.ExampleClass1, Tile",
"Foo": "hello"
},
"PropertyName": "name1",
"CategoryName": null,
"DisplayName": null,
"PropertyId": 0,
"TypeName": "Question42537050.ExampleClass1, Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"ToolTip": "tip1",
"Description": null
}
当然,如果你这样做,第t ype名称将在JSON中出现两次 - 一次为您的TypeName
属性,并且一次为Json.NET的$type
属性。此设置仅适用于复杂的对象和数组,而不适用于基本类型。
无论是哪种情况,出于安全原因,您应该在创建类型实例之前清理TypeName
,原因解释为here。我的代码假设您已经使用custom SerializationBinder
设置JsonSerializer.Binder
来执行此操作,但是您也可以在PropertyValueConverter.ReadJson()
本身中实施一些验证逻辑。
谢谢!由于专有的东西,我不得不做一些修改,但是这个工作很完美! –
如果您收到null
CurrentValue的话,我会检查,看看是否反序列化过程中被抛出任何错误。如果CurrentValue实际上有一个值(我怀疑字符串化的对象可能?),那么您需要编写自定义JsonConverter以使其成为您想要的对象。
是调试器中的CurrentValue null还是会引发错误? – Coder
它实际上显示的是读取的json字符串中的值,但是在反序列化之后,它仅显示一个字符串值,并且大括号包裹在两个或三个键/值对上 –
可以共享您的JSON吗? – Coder