Making custom JsonConverter respect ItemTypeNameHandling when serializing with JSON.NET

泄露秘密 提交于 2019-12-08 02:49:49

问题


I have an object with a list of base class sub-objects. Sub-objects need a custom converter. I can't make my custom converter respect ItemTypeNameHandling option.

Sample code (create a new C# Console project, add JSON.NET NuGet package):

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace My {
    class Program {
        private static void Main () {
            Console.WriteLine(JsonConvert.SerializeObject(
                new Box { toys = { new Spintop(), new Ball() } },
                Formatting.Indented));
            Console.ReadKey();
        }
    }

    [JsonObject] class Box
    {
        [JsonProperty (
            ItemConverterType = typeof(ToyConverter),
            ItemTypeNameHandling = TypeNameHandling.Auto)]
        public List<Toy> toys = new List<Toy>();
    }
    [JsonObject] class Toy {}
    [JsonObject] class Spintop : Toy {}
    [JsonObject] class Ball : Toy {}

    class ToyConverter : JsonConverter {
        public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) {
            serializer.Serialize(writer, value);
        }
        public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
            return serializer.Deserialize(reader, objectType);
        }
        public override bool CanConvert (Type objectType) {
            return typeof(Toy).IsAssignableFrom(objectType);
        }
    }
}

Produced output:

{
  "toys": [
    {},
    {}
  ]
}

Necessary output (this is what happens if I comment ItemConverterType = typeof(ToyConverter), line):

{
  "toys": [
    {
      "$type": "My.Spintop, Serialization"
    },
    {
      "$type": "My.Ball, Serialization"
    }
  ]
}

I've tried temporarily changing value of serializer.TypeNameHandling in ToyConverter.WriteJson method, but it affects unrelated properties. (Of course, my real converter is more complex than that. It's just an example with base functionality.)

Question: How to make my custom JsonConverter respect ItemTypeNameHandling property of JsonProperty attribute?


回答1:


Having delved into the source code for Json.Net (version 4.5 release 11), it looks as though what you want to do is not possible.

The key to getting types written to the output is this method:

Newtonsoft.Json.Serialization.JsonSerializerInternalWriter
    .ShouldWriteType(TypeNameHandling typeNameHandlingFlag, JsonContract contract,
        JsonProperty member, JsonContainerContract containerContract,
        JsonProperty containerProperty)

It's the containerContract and containerProperty parameters that are important here. When serializing without a converter, these parameters are supplied and ShouldWriteType is able to use them to figure out what TypeNameHandling to use.

When serializing with a converter, however, those two parameters are not supplied. This appears to be because ToyConverter.WriteJson results in a call to Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue like this:

SerializeValue(jsonWriter, value, GetContractSafe(value), null, null, null);

Note that the last two parameters are in fact a JsonContainerContract containerContract and JsonProperty containerProperty, and are passed down the chain to the ShouldWriteType method. Therein lies the problem: because they are null, the logic of the ShouldWriteType method means that it returns false, thus the type is not written.

Edit:

Inspired by this, you could workaround this problem by customising the WriteJson method of your converter:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    writer.WriteStartObject();
    writer.WritePropertyName("$type");
    writer.WriteValue(RemoveAssemblyDetails(value.GetType().AssemblyQualifiedName.ToString()));
    writer.WriteEndObject();
}

private static string RemoveAssemblyDetails(string fullyQualifiedTypeName)
{
    StringBuilder builder = new StringBuilder();

    // loop through the type name and filter out qualified assembly details from nested type names
    bool writingAssemblyName = false;
    bool skippingAssemblyDetails = false;
    for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
    {
        char current = fullyQualifiedTypeName[i];
        switch (current)
        {
            case '[':
                writingAssemblyName = false;
                skippingAssemblyDetails = false;
                builder.Append(current);
                break;
            case ']':
                writingAssemblyName = false;
                skippingAssemblyDetails = false;
                builder.Append(current);
                break;
            case ',':
                if (!writingAssemblyName)
                {
                    writingAssemblyName = true;
                    builder.Append(current);
                }
                else
                {
                    skippingAssemblyDetails = true;
                }
                break;
            default:
                if (!skippingAssemblyDetails)
                    builder.Append(current);
                break;
        }
    }

    return builder.ToString();
}

Note that that RemoveAssemblyDetails method is ripped straight from the Json.Net source.

You will of course need to modify the WriteJson method to output the rest of the fields, but hopefully that does the trick.



来源:https://stackoverflow.com/questions/14182961/making-custom-jsonconverter-respect-itemtypenamehandling-when-serializing-with-j

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