反序列化数组中的项目时忽略自定义 JsonConverter

Nok*_*tai 5 c# serialization json json.net deserialization

编辑:制作了一个更简单、更透明的示例案例

我正在尝试反序列化一组组件(属于一个实体)。其中一个组件是 Sprite 组件,它保存纹理和动画信息。我为此实现了一个 CustomConverter,因为原始精灵类有点臃肿,而且没有无参数构造函数(该类来自单独的库,因此我无法修改它)。

实际用例有点复杂,但我在下面添加了一个类似的示例。我测试了代码,出现了同样的问题。反序列化时从不使用 ReadJson。但是当序列化 WriteJson 时确实被调用得非常好。

这些是组件和它的自定义转换器;

 public class ComponentSample
{
    int entityID;
}


public class Rect
{
    public int x;
    public int y;
    public int width;
    public int height;

    public Rect( int x, int y, int width, int height )
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
}

//this class would normally have a texture and a bunch of other data that is hard to serialize
//so we will use a jsonconverter to instead point to an atlas that contains the texture's path and misc data
public class SpriteSample<TEnum> : ComponentSample
{
    Dictionary<TEnum, Rect[]> animations = new Dictionary<TEnum, Rect[]>();

    public SpriteSample( TEnum animationKey, Rect[] frames )
    {
        this.animations.Add( animationKey, frames );
    }
}

public class SpriteSampleConverter : JsonConverter<SpriteSample<int>>
{
    public override SpriteSample<int> ReadJson( JsonReader reader, Type objectType, SpriteSample<int> existingValue, bool hasExistingValue, JsonSerializer serializer )
    {
        JObject jsonObj = JObject.Load( reader );

        //get texturepacker atlas
        string atlasPath = jsonObj.Value<String>( "atlasPath" );

        //some wizardy to get all the animation and load the texture and stuff
        //for simplicity sake I'll just put in some random data
        return new SpriteSample<int>( 99, new Rect[ 1 ] { new Rect( 0, 0, 16, 16 ) } );
    }

    public override void WriteJson( JsonWriter writer, SpriteSample<int> value, JsonSerializer serializer )
    {
        writer.WriteStartObject();

        writer.WritePropertyName( "$type" );
        //actually don't know how to get the type, so I just serialized the SpriteSample<int> to check
        writer.WriteValue( "JsonSample.SpriteSample`1[[System.Int32, mscorlib]], NezHoorn" );

        writer.WritePropertyName( "animationAtlas" );
        writer.WriteValue( "sampleAtlasPathGoesHere" );

        writer.WriteEndObject();
    }
}
Run Code Online (Sandbox Code Playgroud)

它在序列化时正确生成 Json;

        JsonSerializer serializer = new JsonSerializer();

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
        settings.PreserveReferencesHandling = PreserveReferencesHandling.All; //none
        settings.TypeNameHandling = TypeNameHandling.All;
        settings.Formatting = Formatting.Indented;
        settings.MissingMemberHandling = MissingMemberHandling.Ignore;
        settings.DefaultValueHandling = DefaultValueHandling.Ignore;
        settings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;
        settings.Converters.Add( new SpriteSampleConverter() );

        ComponentSample[] components = new ComponentSample[]
        {
            new ComponentSample(),
            new ComponentSample(),
            new SpriteSample<int>(10, new Rect[] { new Rect(0,0,32,32 ) } )
        };

        string fullFile = "sample.json";

        string directoryPath = Path.GetDirectoryName( fullFile );
        if ( directoryPath != "" )
            Directory.CreateDirectory( directoryPath );

        using ( StreamWriter file = File.CreateText( fullFile ) )
        {
            string jsonString = JsonConvert.SerializeObject( components, settings );
            file.Write( jsonString );
        }
Run Code Online (Sandbox Code Playgroud)

杰森:

{
  "$id": "1",
  "$type": "JsonSample.ComponentSample[], NezHoorn",
  "$values": [
    {
      "$id": "2",
      "$type": "JsonSample.ComponentSample, NezHoorn"
    },
    {
      "$id": "3",
      "$type": "JsonSample.ComponentSample, NezHoorn"
    },
    {
      "$type": "JsonSample.SpriteSample`1[[System.Int32, mscorlib]], NezHoorn",
      "animationAtlas": "sampleAtlasPathGoesHere"
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

但是当我尝试反序列化列表时,它从不调用 SpriteSampleConverter 上的 ReadJson,而只是尝试按原样反序列化对象。

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
        settings.TypeNameHandling = TypeNameHandling.All;
        settings.Formatting = Formatting.Indented;
        settings.MissingMemberHandling = MissingMemberHandling.Ignore;
        settings.NullValueHandling = NullValueHandling.Ignore;
        settings.DefaultValueHandling = DefaultValueHandling.Ignore;
        settings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;

        settings.Converters.Add( new SpriteSampleConverter() );

        using ( StreamReader file = File.OpenText( "sample.json" ) )
        {
            //JsonConvert.PopulateObject( file.ReadToEnd(), man, settings );
            JObject componentsJson = JObject.Parse( file.ReadToEnd() );
            //ComponentList components = JsonConvert.DeserializeObject<ComponentList>( componentsJson.ToString(), settings );
            JArray array = JArray.Parse( componentsJson.GetValue( "$values" ).ToString() );
            ComponentSample[] list = JsonConvert.DeserializeObject<ComponentSample[]>( array.ToString(), settings );

            //The SpriteSampleConverter does work here!
            SpriteSample<int> deserializedSprite = JsonConvert.DeserializeObject<SpriteSample<int>>( componentsJson.GetValue( "$values" ).ElementAt(2).ToString(), settings );
        }
Run Code Online (Sandbox Code Playgroud)

我做了一个快速测试,看看 SpriteSampleConverter 是否工作,ReadJson 确实在这里被调用;

SpriteSample deserializedSprite = JsonConvert.DeserializeObject>( componentsJson.GetValue( "$values" ).ElementAt(2).ToString(), settings );

这不是有效的解决方案,因为我不知道对象是否/在哪里会有精灵组件。我猜反序列化到 Component[] 会使序列化器只使用默认转换器?任何想法我可能做错了什么?

编辑 我刚刚尝试了一个非通用的 JsonConverter 来查看 CanConvert 是否被调用,令人惊讶的是它在检查类型 ComponentSample[] 和 ComponentSample 时被调用,但 SpriteSample 从未通过检查。

public class SpriteSampleConverterTwo : JsonConverter
{
    public override bool CanConvert( Type objectType )
    {
        return objectType == typeof( SpriteSample<int> );
    }

    public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
    {
        JObject jsonObj = JObject.Load( reader );

        //get texturepacker atlas
        string atlasPath = jsonObj.Value<String>( "atlasPath" );

        //some wizardy to get all the animation and load the texture and stuff
        //for simplicity sake I'll just put in some random data
        return new SpriteSample<int>( 99, new Rect[ 1 ] { new Rect( 0, 0, 16, 16 ) } );
    }

    public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
    {
        writer.WriteStartObject();

        writer.WritePropertyName( "$type" );
        //actually don't know how to get the type, so I just serialized the SpriteSample<int> to check
        writer.WriteValue( "JsonSample.SpriteSample`1[[System.Int32, mscorlib]], NezHoorn" );

        writer.WritePropertyName( "animationAtlas" );
        writer.WriteValue( "sampleAtlasPathGoesHere" );

        writer.WriteEndObject();
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望 II 可以查看 json.net 的源代码,但是我在运行它时遇到了很多麻烦。

Nok*_*tai 2

我查看了 json.net 源代码并得出结论:只有数组声明的类型将用于检查转换器;

        JsonConverter collectionItemConverter = GetConverter(contract.ItemContract, null, contract, containerProperty);

        int? previousErrorIndex = null;

        bool finished = false;
        do
        {
            try
            {
                if (reader.ReadForType(contract.ItemContract, collectionItemConverter != null))
                {
                    switch (reader.TokenType)
                    {
                        case JsonToken.EndArray:
                            finished = true;
                            break;
                        case JsonToken.Comment:
                            break;
                        default:
                            object value;

                            if (collectionItemConverter != null && collectionItemConverter.CanRead)
                            {
                                value = DeserializeConvertable(collectionItemConverter, reader, contract.CollectionItemType, null);
                            }
Run Code Online (Sandbox Code Playgroud)

它不会检查每个单独的数组项的类型来查找相应的转换器。

所以我需要找到一种不同于使用转换器的解决方案。