5

デシリアライズ時にのみ使用したいコンバーターがあります。そこで、CanWriteをfalseに設定しました。これは正常に機能し、すべてが正常にシリアル化されます。次に、Json文字列には、SantaClauseアイテムの配列とそれらが具象型SantaClauseであることを示す$typeを持つSantaClauseCollectionが含まれるオブジェクトグラフが含まれます。

ただし、逆シリアル化中にSantaClausのコレクションに遭遇すると、CanConvertを呼び出すことはありません(SantaClausCollectionを確認し、F5キーを押して続行します。その後、SantaClausのコレクション内のアイテムに遭遇すると、ブレークポイントに再び到達するはずです。しかし、そうではありません)。SantaClausアイテムに到達したときに、CanConvertを呼び出そうとはしていません。そのアイテムに対してCanConvertを呼び出して、コンバーターがそれを処理するかどうかを確認しなくても、代わりにそれ自体を逆シリアル化しようとします。これは、クラスにデフォルトコンストラクターがなく、プロパティ名が一致する規則を持つコンストラクターがないため、機能しません。

SantaClaus型に使用するコンストラクターが見つかりません。クラスには、デフォルトのコンストラクター、引数を持つ1つのコンストラクター、またはJsonConstructor属性でマークされたコンストラクターのいずれかが必要です。

このエラーが発生する理由は理解できますが、問題は、Json.netがCanConvertを呼び出して、コンバーターが逆シリアル化を処理するかどうかを確認するのではなく、オブジェクトを逆シリアル化しようとしたことを示しています。

コレクション内のアイテムごとにCanConvertが呼び出されないのはなぜですか?

私のコンバーター:

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

    /// <summary>
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally.
    /// </summary>       
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize<SantaClausEx>(reader);
    }

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


    public override bool CanRead
    {
        get
        {
            return true;
        }
    }

    public override bool CanWrite
    {
        get
        {
            return false;//We only need this converter when reading.
        }
    }

}

SantaClausExは、SantaClausから継承して、プロパティに一致するように名前が変更されたパラメーターを持つコンストラクターを追加します。

class SantaClaus //a third party class I can't modify
{
    string Name {get;set;}
    public SantaClaus(string santaClauseName) { this.Name = santaClauseName }
}

class SantaClausEx:SantaClaus 
{
    //provide a constructor with param names matching property names
    public SantaClausEx(string name) : base(name)
}

Json.netはSantaClausを逆シリアル化できませんが、SantaClauseExを逆シリアル化できます。

私はそのSantaClauseExクラスをどこでも使用していて、問題なく動作しますが、これを自動的に行うコンバーターを作成したかったのです。

これは、Jsonがコレクションに対してどのように見えるかです。

SantaClausCollection: [
{
  $type: "SantaClaus, XMasClasses.NET20"
  Name: "St. Bob"
},
{
  $type: "SantaClaus, XMasClasses.NET20"
  Name: "St. Jim"
}
]
4

3 に答える 3

1

基本クラスから継承されたオブジェクトの逆シリアル化にも同様の問題がありました(SantaClauseExオブジェクトを逆シリアル化する必要がある方法と同様ですが、すべてSantaClauseオブジェクトとして定義されています)。問題は、JSon.Netがサブタイプを識別できないことにあります。

stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net-to-deserialize-a-list-of-baseを参照してください

于 2013-11-26T21:01:27.640 に答える
1

コンバーターをConverters設定オブジェクトのコレクションに追加したと思います。

動作するコンバーターで簡単なテストを書きました

public class SantaClausJsonTest
{
    public SantaClausJsonTest()
    {
        Settings = new JsonSerializerSettings();
        Settings.TypeNameHandling = TypeNameHandling.Objects;
        Settings.Converters.Add(new SantaClaus2JsonConverter());
    }

    private JsonSerializerSettings Settings;

    [Fact]
    public void SerializeAndDeserialize()
    {
        var collection = new []
            {
                new SantaClaus("St. Bob"),
                new SantaClaus("St. Jim"),
            };

        var serialized = JsonConvert.SerializeObject(collection, Settings);

        Console.WriteLine(serialized);
        Assert.False(string.IsNullOrEmpty(serialized));

        var deserialized = JsonConvert.DeserializeObject<SantaClaus[]>(serialized, Settings);

        Console.WriteLine(deserialized.GetType().ToString());
        Assert.NotNull(deserialized);
        Assert.True(deserialized.Any(a => a.Name == "St. Bob"));
        Assert.True(deserialized.Any(a => a.Name == "St. Jim"));
    }
}

public class SantaClaus
{
    public SantaClaus(string santaClauseName)
    {
        Name = santaClauseName;
    }

    public string Name { get; private set; }
}

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

    /// <summary>
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally.
    /// </summary>       
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var name = string.Empty;

        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.String && reader.Path.EndsWith("Name"))
            {
                name = reader.Value as string;
            }
            if (reader.TokenType == JsonToken.EndObject)
            {
                break;
            }
        }

        return Activator.CreateInstance(objectType, name);
    }

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


    public override bool CanRead
    {
        get
        {
            return true;
        }
    }

    public override bool CanWrite
    {
        get
        {
            return false;//We only need this converter when reading.
        }
    }
于 2013-11-26T22:06:17.493 に答える
0

Rudusの回答が機能するようになったら、最初の試みで問題を特定しました。彼は、デフォルトコンストラクターのない型がある場合に最適ですが、プロパティ値を他のコンストラクターの1つにマップでき、私の特定のケースでは確かに簡単です。

なんらかの理由で、逆シリアル化するときに別のタイプを作成するという、私が最初にやろうとしていたことのようなものが本当に必要な場合は、それを機能させることができました。

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

    /// <summary>
    /// Deserializes a SantaClaus as a SantaClausEx which has a matching constructor that allows it to deserialize naturally.
    /// </summary>       
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //temporarily switch off name handling so it ignores "SantaClaus" type when
        //explicitely deserialize as SantaClausEx
        //This could cause issues with nested types however in a more complicated object graph
        var temp = serializer.TypeNameHandling;
        serializer.TypeNameHandling = TypeNameHandling.None;
        var desr = serializer.Deserialize<SantaClausEx>(reader);
        serializer.TypeNameHandling = temp;//restore previous setting

        return desr;
    }

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

    public override bool CanRead { get { return true; } }

    public override bool CanWrite { get { false; } } //only for reading

}
于 2013-11-26T23:37:47.787 に答える