.NET Cannot parse nested object when json deserializing

I know that there are a lot of posts about this situation, but all the answers I was looking for do not work for me, and I think my situation is a little different.

I have a class known as PropertyValue, and this is a metadata class that describes the value of a property, and then also has an attribute that contains the actual value:

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(), , CurrentValue. switch , , , JSON.

?

: JSON, :

{
   "PropertyName":"TimeZone",
   "CategoryName":"TBD",
   "DisplayName":"TimeZone",
   "PropertyId":15,
   "TypeName":"Namespace.TimeZoneReference",
   "ToolTip":"",
   "Description":"",
   "CurrentValue":{
      "timeZoneID":21,
      "timeZoneName":"Eastern Standard Time"
   }
}
+4
2

, Json.NET TypeNameHandling. , 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
        // /questions/82307/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. ( read/write, , .)

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
  }

, , JSON - TypeName Json.NET $type. , .

, , TypeName , . , JsonSerializer.Binder, , custom SerializationBinder, PropertyValueConverter.ReadJson().

+3

null CurrentValue, , . CurrentValue (, , ?), JsonConverter, , .

0

Source: https://habr.com/ru/post/1671159/


All Articles