web-dev-qa-db-ja.com

JSON.NETの特定のオブジェクトのカスタム変換

一部のオブジェクトをシリアル化するためにJSON.NETを使用しています。特定のオブジェクトに対してのみデフォルトのjson.netコンバーターをオーバーライドする簡単な方法があるかどうか知りたいですか?

現在、私は次のクラスを持っています:

public class ChannelContext : IDataContext
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<INewsItem> Items { get; set; }
}

JSON.NETは現在、上記を次のようにシリアル化します。

{
    "Id": 2,
    "Name": "name value",
    "Items": [ item_data_here ]
}

その特定のクラスが代わりにこのようにフォーマットすることは可能ですか?

"Id_2":
{
    "Name": "name value",
    "Items": [ item data here ]
}

私はJSON.NETに少し慣れていません。上記がカスタムコンバーターの記述と関係があるのか​​と思っていました。具体的な例を見つけることができませんでした。特定の情報源を教えてもらえれば、本当にありがたいです。

上記のコンテキストは、JSON.NETのデフォルトコンバーターが正常に変換するさらに大きなコンテキストの一部であるため、特定のクラスを常に同じに変換するソリューションを見つける必要があります。

私の質問が十分に明確であることを願っています...

更新:

新しいカスタムコンバーターを作成する方法を見つけました(JsonConverterから継承し、その抽象メソッドをオーバーライドする新しいクラスを作成することにより)、次のようにWriteJsonメソッドをオーバーライドしました。

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        ChannelContext contextObj = value as ChannelContext;

        writer.WriteStartObject();
        writer.WritePropertyName("id_" + contextObj.Id);
        writer.WriteStartObject();
        writer.WritePropertyName("Name");
        serializer.Serialize(writer, contextObj.Name);

        writer.WritePropertyName("Items");
        serializer.Serialize(writer, contextObj.Items);
        writer.WriteEndObject();
        writer.WriteEndObject();
    }

これは確かにうまく機能しますが、jsonwriterを使用してオブジェクトを手動で「書き込む」のではなく、デフォルトのJsonSerializer(またはそのコンバーター)を再利用して、残りのオブジェクトプロパティをシリアル化する方法があるかどうかに興味があります。メソッド。

PDATE 2:より一般的な解決策を模索していて、次のことを思いつきました。

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

        // Write associative array field name
        writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(value));

        // Remove this converter from serializer converters collection
        serializer.Converters.Remove(this);

        // Serialize the object data using the rest of the converters
        serializer.Serialize(writer, value);

        writer.WriteEndObject();
    }

これは、次のようにコンバータを手動でシリアライザに追加するときにうまく機能します。

jsonSerializer.Converters.Add(new AssociativeArraysConverter<DefaultFieldNameResolver>());
jsonSerializer.Serialize(writer, channelContextObj);

ただし、実行時に発生する自己参照ループのため、ChannelContextクラスの上のカスタムカバターに設定された[JsonConverter()]属性を使用すると機能しません。

serializer.Serialize(writer, value)

これは明らかに、JsonConverterAttributeで設定されたカスタムコンバーターがクラスのデフォルトコンバーターと見なされるようになり、無限ループが発生するためです。この問題を解決するには、基本的なjsonconverterクラスを継承し、代わりにbase.serialize()メソッドを呼び出すだけです。しかし、そのようなJsonConverterクラスは存在するのでしょうか。

どうもありがとう!

マイキー

31
Mikey S.

誰かが私の解決策に興味がある場合:

特定のコレクションをシリアル化するとき、標準のjson配列の代わりに連想json配列を作成したかったので、同僚のクライアント側の開発者は、フィールドを反復処理する代わりに、名前(またはその問題のキー)を使用して効率的にフィールドにアクセスできます。

以下を検討してください。

public class ResponseContext
{
    private List<ChannelContext> m_Channels;

    public ResponseContext()
    {
        m_Channels = new List<ChannelContext>();
    }

    public HeaderContext Header { get; set; }

    [JsonConverter(
        typeof(AssociativeArraysConverter<ChannelContextFieldNameResolver>))]
    public List<ChannelContext> Channels
    {
        get { return m_Channels; }
    }

}

[JsonObject(MemberSerialization = MemberSerialization.OptOut)]
public class ChannelContext : IDataContext
{
    [JsonIgnore]
    public int Id { get; set; }

    [JsonIgnore]
    public string NominalId { get; set; }

    public string Name { get; set; }

    public IEnumerable<Item> Items { get; set; }
}

応答コンテキストには、クライアントに書き戻される応答全体が含まれます。これには、「チャネル」というセクションが含まれています。通常の配列でチャネルコンテキストを出力する代わりに、次の方法:

"Channels"
{
"channelNominalId1":
{
  "Name": "name value1"
  "Items": [ item data here ]
},
"channelNominalId2":
{
  "Name": "name value2"
  "Items": [ item data here ]
}
}

上記を他のコンテキストにも使用したかったので、「キー」として別のプロパティを使用することを決定したり、独自の一意の名前を作成することを選択したりすることがあります。ある種の汎用ソリューションが必要だったため、次の方法でJsonConverterから継承するAssociativeArraysConverterという汎用クラスを作成しました。

public class AssociativeArraysConverter<T> : JsonConverter
    where T : IAssociateFieldNameResolver, new()
{
    private T m_FieldNameResolver;

    public AssociativeArraysConverter()
    {
        m_FieldNameResolver = new T();
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IEnumerable).IsAssignableFrom(objectType) &&
                !typeof(string).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        IEnumerable collectionObj = value as IEnumerable;

        writer.WriteStartObject();

        foreach (object currObj in collectionObj)
        {
            writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(currObj));
            serializer.Serialize(writer, currObj);
        }

        writer.WriteEndObject();
    }
}

そして、次のインターフェースを宣言しました:

public interface IAssociateFieldNameResolver
{
    string ResolveFieldName(object i_Object);
}

これで、あとはすべて、IAssociateFieldNameResolverの単一の関数を実装するクラスを作成します。これは、コレクション内の各項目を受け入れ、そのオブジェクトに基づいて文字列を返します。これは、項目の関連オブジェクトのキーとして機能します。

そのようなクラスの例は次のとおりです。

public class ChannelContextFieldNameResolver : IAssociateFieldNameResolver
{
    public string ResolveFieldName(object i_Object)
    {
        return (i_Object as ChannelContext).NominalId;
    }
}
28
Mikey S.