web-dev-qa-db-ja.com

JSON.NETを使用してオブジェクトの動的プロパティ名をシリアル化する

REST APIに接続するためにオブジェクトのシリアル化にJSON.NETを使用しています。JSONにシリアル化する必要があるオブジェクトのプロパティの1つに動的プロパティ名があります。このプロパティの構造に含まれる値が数値の場合、JSONプロパティは「type_id」ですが、この値が文字列値の場合、JSONプロパティ名は「type_code」です。カスタムJsonConverterがありますが、シリアル化しようとすると、このメッセージとともにJsonWriterExceptionが表示されます。

"状態PropertyのトークンPropertyNameは無効なJSONオブジェクトになります。パス ''。"

以下は私のオブジェクトのサブセットです。以下に示すように、オブジェクトにプロパティ名を指定していません。

_[JsonProperty("title",Required=Required.Always,Order=1)]
public string Title { get; set; }

[JsonProperty("date",Order=3)]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime Date { get; set; }

[JsonProperty(Order=2)]
[JsonConverter(typeof(TypeIdentifierJsonConverter))]
public TypeIdentifier DocTypeIdentifier { get; set; }
_

TypeIdentifierクラスでは、WriteJson()メソッドに次のものがあります。

_public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    TypeIdentifier docTypeId;
    id= (TypeIdentifier) value;

    writer.WritePropertyName(id.ParameterName);
    writer.WriteValue(id.Value);           
}
_

ただし、デフォルトでは、オブジェクトのプロパティの名前がカスタムプロパティの名前ではなく、JSON文字列内の単一の値に2つのプロパティ名が含まれると想定しています。 JsonPropertyAttributeタグは、明示的に指定されていない場合にオブジェクトのプロパティ名をプルするように見えるため、このためにプロパティ名を動的に設定するにはどうすればよいですか?

注:このオブジェクトは、このアプリから逆シリアル化する必要はありません。

EDIT:このオブジェクトは[JsonObject(MemberSerialization.OptIn)]属性でタグ付けされています

14
JNYRanger

JsonConverterは、親オブジェクトのプロパティの名前を設定できません。コンバーターのWriteJsonメソッドが呼び出されたとき、プロパティ名はすでにJSONに書き込まれています。作家はその点だけを期待しています。そのため、エラーが発生します。これを機能させるには、親オブジェクト用にカスタムコンバーターを作成する必要があります。そのコンバーターは、その子のプロパティ名と値を書き込む責任があります。

フォローアップ

親オブジェクトに適用されたJSON属性が引き続き尊重されるように、親オブジェクトのコンバーターを記述しながら、必要な結果を得ることができます。以下にアプローチの概要を説明します。

まず、セットアップについて少し説明します。クラスの名前を言わなかったので、この例では、Documentと呼ばれていると仮定します。それに実質的な変更を1つ加えるだけで済みます。つまり、DocTypeIdentifierプロパティから[JsonConverter]属性を削除します。だから私たちは持っています:

[JsonObject(MemberSerialization.OptIn)]
class Document
{
    [JsonProperty("title", Required = Required.Always, Order = 1)]
    public string Title { get; set; }

    [JsonProperty("date", Order = 3)]
    [JsonConverter(typeof(IsoDateTimeConverter))]
    public DateTime Date { get; set; }

    [JsonProperty(Order = 2)]
    public TypeIdentifier DocTypeIdentifier { get; set; }

    public string OtherStuff { get; set; }
}

また、TypeIdentifierクラスのコードも表示しなかったので、例として、次のように想定します。

class TypeIdentifier
{
    public string Value { get; set; }
    public string ParameterName { get; set; }
}

それが邪魔にならないので、コンバーターを作ることができます。アプローチはかなり単純です。適用された属性を尊重するという事実を利用して、DocumentJObjectにロードし、特別な処理が必要なため、戻ってDocTypeIdentifierのシリアル化を修正します。それができたら、JObjectJsonWriterに書き出します。コードは次のとおりです。

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Document doc = (Document)value;

        // Create a JObject from the document, respecting existing JSON attribs
        JObject jo = JObject.FromObject(value);

        // At this point the DocTypeIdentifier is not serialized correctly.
        // Fix it by replacing the property with the correct name and value.
        JProperty prop = jo.Children<JProperty>()
                           .Where(p => p.Name == "DocTypeIdentifier")
                           .First();

        prop.AddAfterSelf(new JProperty(doc.DocTypeIdentifier.ParameterName, 
                                        doc.DocTypeIdentifier.Value));
        prop.Remove();

        // Write out the JSON
        jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

これでコンバーターができましたが、問題は、Documentクラスを[JsonConverter]属性で単純に装飾して使用できないことです。そうした場合、ドキュメントをJObjectにロードしたときにコンバーターがそれ自体を使用しようとしたため、再帰ループが発生します。したがって、代わりに、コンバーターのインスタンスを作成し、設定を介してシリアライザーに渡す必要があります。コンバーターのCanConvertメソッドは、正しいクラスで使用されることを保証します。 JObject.FromObjectメソッドは内部で別のシリアライザーインスタンスを使用するため、DocumentConverterを認識しないため、問題が発生しません。

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new DocumentConverter());

string json = JsonConvert.SerializeObject(doc, settings);

これは、コンバーターの動作を示すデモです。

class Program
{
    static void Main(string[] args)
    {
        Document doc = new Document
        {
            Title = "How to write a JSON converter",
            Date = DateTime.Today,
            DocTypeIdentifier = new TypeIdentifier
            {
                ParameterName = "type_id",
                Value = "26"
            },
            OtherStuff = "this should not appear in the JSON"
        };

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(new DocumentConverter());
        settings.Formatting = Formatting.Indented;

        string json = JsonConvert.SerializeObject(doc, settings);
        Console.WriteLine(json);
    }
}

上記の出力は次のとおりです。

{
  "title": "How to write a JSON converter",
  "type_id": "26",
  "date": "2014-03-28T00:00:00-05:00"
}
22
Brian Rogers