JsonConverters and object properties

偶尔善良 提交于 2021-02-10 12:12:31

问题


I'm trying to find an answer to a problem that has stumped me for a few days, I'm converting some legacy WCF code over to SignalR, it's an internal API and the client is Silverlight for the moment.

I have a .NET class (Content1) on the server side that has a matching JSON converter and a message class (Message1) that has a property of type Content1. This all seems to work fine, the problem is when that property is changed to type object (which the current code is)

public class Message1
{
    public string Name { get; set; }

    public object Payload { get; set; }
}

Now my custom JsonConverter is no longer called.

I've placed the converter in the JsonSerializer.Converters collection and I can see the CanConvert method being hit on the converter, but the property comes through as a request to convert System.Object.

I've enabled typeNameHandling and set it to Auto (All/Object/Array are not an option as SignalR breaks), and can see the $type property being written into my JSON

{
  "Name": "Test Message 1",
  "Payload": {
    "$type": "Models.Content1, Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0d90b1aaa82178d3",
    "Propert1": "This is a string"
  }
}

and with tracing turned on I can see the type being resolved

Resolved type 'Models.Content1, Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0d90b1aaa82178d3to Models.Content1. Path 'Payload.$type'.

But my custom converter is never called.

So the crux of my issue is, is there a way to get Json.Net to delegate to my class level custom converter when using $type?

Or failing that, if I write a custom converter and register it for my object property

public class Message1
{
    public string Name { get; set; }

    [JsonConverter(typeof(YetAnotherConverter))]
    public object Payload { get; set; }
}

Is there a way to peek ahead at the type of the object via the $type property, I'd really like to do this

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var data = JObject.Load(reader);
        var type = data.Property("$type");
        if (type.Value.Value<string>().Contains("Content1"))
        {
            var obj = serializer.Deserialize<Content1>(reader);
            return obj;
        }
        if (type.Value.Value<string>().Contains("Content2"))
        {
            var obj = serializer.Deserialize<Content2>(reader);
            return obj;
        }

        return serializer.Deserialize(reader);
    }

Which would invoke the correct JsonConverters for my type, but in reality does not work as JsonReader is forward only so I can't 'peek' at the type.

I suppose I could go the route of making the json serialise more like below

{
  "Name": "Test Message 1",
  "Payload": {
    "$type": "Models.Content1, Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0d90b1aaa82178d3",
    "data": {
      "Propert1": "This is a string"
    }
  }
}

Then I can edge forward through the JSON with the reader, get the $type (should probably use a different name at this point as I've completely mangled in original use), find the data section and then pass that off to the serialiser with the correct object type so it would invoke the converter in the class level attribute.

But this honestly feels like I'm heading of down a rabbit hole so deep, it can't be right!

Thanks

Stephen.


回答1:


Looks like the converter for the polymorphic type is not called in this case.

What you can do instead is to create your YetAnotherConverter; in ReadJson load the object into a JToken, parse the "$type" property, then call JToken.ToObject(type, serializer) to deserialize the intermediate JToken to your final type. This ensures its converter gets called. Thus:

public class PolymorphicConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(object);
    }

    public override bool CanWrite { get { return false; } }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        if (token.Type != JTokenType.Object)
            return token;
        var typeString = (string)token["$type"];
        if (typeString == null)
            return token;
        string typeName, assemblyName;
        SplitFullyQualifiedTypeName(typeString, out typeName, out assemblyName);
        var type = serializer.Binder.BindToType(assemblyName, typeName);
        if (type != null)
            return token.ToObject(type, serializer);
        return token;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    // 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.Value).Trim();
            assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.Value + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.Value - 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;
    }
}

And then:

public class Message1
{
    public string Name { get; set; }

    [JsonConverter(typeof(PolymorphicConverter))]
    public object Payload { get; set; }
}


来源:https://stackoverflow.com/questions/30174991/jsonconverters-and-object-properties

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!