.NET反序列化JSON时无法解析嵌套对象

时间:2017-03-01 16:31:15

标签: c# 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"
   }
}

2 个答案:

答案 0 :(得分:3)

听起来你正试图重新发明Json.NET的TypeNameHandling setting。由于您未使用此设置,而是自行序列化CurrentValue的类型名称,因此您需要创建custom JsonConverter来填充PropertyValue并反序列化{ {1}}到所需的类型。如果没有这个,Json.NET会将当前值反序列化为基元(例如CurrentValuelong)或LINQ to JSON对象(例如JObject)以用于非基元JSON值。 (后者是字符串值,大括号围绕着你在评论中看到的两个或三个键/值对。)

以下是适用于您的类型的一种可能的转换器:

string

示例fiddle。 (我必须读取/写入几个属性,因为它们是只读的,但未在构造函数中设置。)

或者,您可以使用[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://stackoverflow.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; } } 标记CurrentValue

[JsonProperty(TypeNameHandling = TypeNameHandling.All)]

完成此操作后,Json.NET将输出一个public sealed class PropertyValue { [JsonProperty(TypeNameHandling = TypeNameHandling.All)] public object CurrentValue { get; set; } // Remainder as before 属性,指示"$type"的实际类型,并在反序列化期间自动使用该类型,例如:

CurrentObject

当然,如果这样做,类型名称将在JSON中出现两次 - 一次用于 { "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 } 属性,一次用于Json.NET的TypeName属性。此设置仅适用于复杂对象和数组,而不适用于基本类型。

在任何一种情况下,出于安全原因,您应该在创建该类型的实例之前清理$type,原因如here所述。我的代码假定您已使用JsonSerializer.Binder设置custom SerializationBinder来执行此操作,但您可以在TypeName本身中实现一些验证逻辑。

答案 1 :(得分:0)

如果你得到class MyResourceManager { private readonly ResourceManager _manager; public MyResourceManager() { _manager = new ResourceManager(typeof(MyResourceManager)) } public string GetStringResouce(string name) { return _manager.GetString(name); } } CurrentValue,那么我会检查在反序列化过程中是否有任何错误被抛出。如果CurrentValue确实有一个值(我怀疑是一个字符串化的对象?),那么你需要编写一个自定义JsonConverter来获得你想要的对象。