2

基本クラスのサブオブジェクトのリストを持つオブジェクトがあります。サブオブジェクトにはカスタム コンバーターが必要です。カスタム コンバーターでItemTypeNameHandlingオプションを尊重することはできません。

サンプル コード (新しい C# コンソール プロジェクトを作成し、JSON.NET NuGet パッケージを追加します):

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);
        }
    }
}

生成された出力:

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

必要な出力 (行にコメントを付けるとこうなりItemConverterType = typeof(ToyConverter),ます):

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

serializer.TypeNameHandlinginメソッドの値を一時的に変更してみましToyConverter.WriteJsonたが、無関係なプロパティに影響します。(もちろん、私の実際のコンバーターはそれよりも複雑です。これは基本的な機能を備えた単なる例です。)

質問:カスタム属性のプロパティをJsonConverter尊重させるにはどうすればよいですか?ItemTypeNameHandlingJsonProperty

4

1 に答える 1

4

Json.Net (バージョン 4.5 リリース 11) のソース コードを詳しく調べると、やりたいことはできないようです。

出力に書き込まれる型を取得するための鍵は、次のメソッドです。

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

ここで重要なのはcontainerContractcontainerPropertyパラメータです。コンバーターなしでシリアル化する場合、これらのパラメーターが提供され、それらShouldWriteTypeを使用して何TypeNameHandlingを使用するかを判断できます。

ただし、コンバーターでシリアル化する場合、これら 2 つのパラメーターは提供されません。ToyConverter.WriteJsonこれは、次のように呼び出しが行われるためと思われますNewtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue

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

最後の 2 つのパラメーターは実際には aJsonContainerContract containerContractとであり、チェーンを介してメソッドJsonProperty containerPropertyに渡されることに注意してください。ShouldWriteTypeそこに問題があります。これらは null であるため、ShouldWriteTypeメソッドのロジックは を返すことを意味するfalseため、型は書き込まれません。

編集:

これに触発されて、WriteJsonコンバーターのメソッドをカスタマイズすることで、この問題を回避できます。

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();
}

RemoveAssemblyDetailsこのメソッドは Json.Netソースから直接取り出されていることに注意してください。

もちろん、残りのフィールドを出力するようにメソッドを変更する必要がありWriteJsonますが、うまくいけばうまくいきます。

于 2013-01-17T09:58:53.893 に答える