.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" 
    } 
} 
+1

是调试器中的CurrentValue null还是会引发错误? – Coder

+0

它实际上显示的是读取的json字符串中的值,但是在反序列化之后,它仅显示一个字符串值,并且大括号包裹在两个或三个键/值对上 –

+0

可以共享您的JSON吗? – Coder

这听起来像你试图重新发明Json.NET的TypeNameHandling setting。由于您没有使用此设置,而是自行序列化CurrentValue的类型名称,因此您需要创建custom JsonConverter以填充PropertyValue并将CurrentValue反序列化为所需的类型。否则,Json.NET会将当前值反序列化为基元(如longstring)或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()本身中实施一些验证逻辑。

+0

谢谢!由于专有的东西,我不得不做一些修改,但是这个工作很完美! –

如果您收到null CurrentValue的话,我会检查,看看是否反序列化过程中被抛出任何错误。如果CurrentValue实际上有一个值(我怀疑字符串化的对象可能?),那么您需要编写自定义JsonConverter以使其成为您想要的对象。