web-dev-qa-db-ja.com

Json.Netを使用して、追加のプロパティを使用してカスタムコレクションをシリアル化/逆シリアル化する方法

以下に示すようないくつかのカスタムプロパティを持つカスタムコレクション(IListを実装)があります。

class FooCollection : IList<Foo> {

    private List<Foo> _foos = new List<Foo>();
    public string Bar { get; set; }        

    //Implement IList, ICollection and IEnumerable members...

}

シリアル化するときは、次のコードを使用します。

JsonSerializerSettings jss = new JsonSerializerSettings() {
    TypeNameHandling = TypeNameHandling.Auto
};
string serializedCollection = JsonConvert.SerializeObject( value , jss );

すべてのコレクションアイテムを適切にシリアル化および逆シリアル化します。ただし、FooCollectionクラスの追加のプロパティは考慮されません。

とにかくそれらをシリアル化に含めることはありますか?

23
Pierluc SS

問題は次のとおりです。オブジェクトがIEnumerableを実装すると、JSON.netはそれを値の配列として識別し、配列に従ってシリアル化します Json構文 (プロパティは含まれません)、例えば:

 [ {"FooProperty" : 123}, {"FooProperty" : 456}, {"FooProperty" : 789}]

プロパティを保持してシリアル化する場合は、カスタムJsonConverterを定義して、そのオブジェクトのシリアル化を手動で処理する必要があります。

// intermediate class that can be serialized by JSON.net
// and contains the same data as FooCollection
class FooCollectionSurrogate
{
    // the collection of foo elements
    public List<Foo> Collection { get; set; }
    // the properties of FooCollection to serialize
    public string Bar { get; set; }
}

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

    public override object ReadJson(
        JsonReader reader, Type objectType, 
        object existingValue, JsonSerializer serializer)
    {
        // N.B. null handling is missing
        var surrogate = serializer.Deserialize<FooCollectionSurrogate>(reader);
        var fooElements = surrogate.Collection;
        var fooColl = new FooCollection { Bar = surrogate.Bar };
        foreach (var el in fooElements)
            fooColl.Add(el);
        return fooColl;
    }

    public override void WriteJson(JsonWriter writer, object value, 
                                   JsonSerializer serializer)
    {
        // N.B. null handling is missing
        var fooColl = (FooCollection)value;
        // create the surrogate and serialize it instead 
        // of the collection itself
        var surrogate = new FooCollectionSurrogate() 
        { 
            Collection = fooColl.ToList(), 
            Bar = fooColl.Bar 
        };
        serializer.Serialize(writer, surrogate);
    }
}

次に、次のように使用します。

var ss = JsonConvert.SerializeObject(collection, new FooCollectionConverter());

var obj = JsonConvert.DeserializeObject<FooCollection>(ss, new FooCollectionConverter());
25
digEmAll

個人的には、可能な限りカスタムJsonConvertersを作成することを避け、代わりにこの目的のために設計されたさまざまなJSON属性を利用するのが好きです。 FooCollectionJsonObjectAttribute で装飾するだけで、配列ではなくJSONオブジェクトとしてシリアル化を強制できます。 CountプロパティとIsReadOnlyプロパティをJsonIgnoreで装飾して、出力に表示されないようにする必要があります。 _foosをプライベートフィールドにしておきたい場合は、JsonPropertyで装飾する必要もあります。

[JsonObject]
class FooCollection : IList<Foo> {
    [JsonProperty]
    private List<Foo> _foos = new List<Foo>();
    public string Bar { get; set; }  

    // IList implementation
    [JsonIgnore]
    public int Count { ... }
    [JsonIgnore]
    public bool IsReadOnly { ... }
}

シリアル化すると、次のような結果になります。

{
  "_foos": [
    "foo1",
    "foo2"
  ],
  "Bar": "bar"
}

明らかに、これは、これらの属性を追加するためにFooCollectionの定義を変更できる場合にのみ機能します。それ以外の場合は、カスタムコンバーターを使用する必要があります。

15
Jeff E

カスタムJsonConverterを記述したくない場合、またはJSON属性( JsonObjectAttribute )を使用したくない場合は、次の拡張メソッドを使用できます。

public static string ToFooJson<T>(this FooCollection fooCollection)
{
     return JsonConvert.SerializeObject(new
     {
         Bar = fooCollection.Bar,
         Collection = fooCollection
     });
}
2
41D0

リストまたはコレクション自体のコンテンツも保持したい場合は、プロパティを公開してリストを返すことを検討できます。シリアル化中の周期的な問題を防ぐために、ラッピングする必要があります。

_[JsonObject]
public class FooCollection : List<int>
{
    [DataMember]
    public string Bar { get; set; } = "Bar";
    public ICollection<int> Items => new _<int>(this);
}

public class _<T> : ICollection<T>
{
    public _(ICollection<T> collection) => Inner = collection;    
    public ICollection<T> Inner { get; }    
    public int Count => this.Inner.Count;    
    public bool IsReadOnly => this.Inner.IsReadOnly;    
    public void Add(T item) => this.Inner.Add(item);    
    public void Clear() => this.Inner.Clear();    
    public bool Contains(T item) => this.Inner.Contains(item);    
    public void CopyTo(T[] array, int arrayIndex) => this.Inner.CopyTo(array, arrayIndex);    
    public IEnumerator<T> GetEnumerator()=> this.Inner.GetEnumerator();
    public bool Remove(T item) => this.Inner.Remove(item);    
    IEnumerator IEnumerable.GetEnumerator() => this.Inner.GetEnumerator();
}
_

_new FooCollection { 1, 2, 3, 4, 4 }_ =>

_{
  "Bar": "Bar",
  "Items": [
    1,
    2,
    3
  ],
  "Capacity": 4,
  "Count": 3
}
_

new FooCollection { 1, 2, 3 }.ToArray() => new []{1, 2, 3}.ToArray()

1
Ahmed Alejo

リストからの継承は機能しますか?

class FooCollection : List<Foo>, IList<Foo>
{
    public string Bar { get; set; }        
    //Implement IList, ICollection and IEnumerable members...
}
1
qujck