30

以下に示すように、いくつかのカスタムプロパティを持つカスタムコレクション(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クラス内の追加のプロパティは考慮されません。

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

4

6 に答える 6

29

問題は次のとおりです。オブジェクトが実装される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());
于 2013-01-17T17:34:26.117 に答える
20

個人的にはJsonConverter、可能な限りカスタムを作成することを避け、代わりにこの目的のために設計されたさまざまな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ます。それ以外の場合は、カスタムコンバーターの方法を使用する必要があります。

于 2016-08-26T19:43:00.410 に答える
3

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

注:このソリューションは、シリアル化/逆シリアル化の両方をサポートます

[JsonObject]
public class FooCollection : List<int>
{
    public string Bar { get; set; } = "Bar";

    [JsonProperty]
    ICollection<int> Items => new _<int>(this);
}

internal class _<T> : ICollection<T>
{
    public _(ICollection<T> collection) => inner = collection;
    private ICollection<T> inner;
    int ICollection<T>.Count => inner.Count;
    bool ICollection<T>.IsReadOnly => inner.IsReadOnly;
    void ICollection<T>.Add(T item) => inner.Add(item);
    void ICollection<T>.Clear() => inner.Clear();
    bool ICollection<T>.Contains(T item) => inner.Contains(item);
    void ICollection<T>.CopyTo(T[] array, int arrayIndex) => inner.CopyTo(array, arrayIndex);
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => inner.GetEnumerator();
    bool ICollection<T>.Remove(T item) => inner.Remove(item);
    IEnumerator IEnumerable.GetEnumerator() => 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()

于 2017-08-19T14:50:53.783 に答える
3

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

public static string ToFooJson<T>(this FooCollection fooCollection)
{
     return JsonConvert.SerializeObject(new
     {
         Bar = fooCollection.Bar,
         Collection = fooCollection
     });
}
于 2019-09-09T23:02:45.173 に答える
1

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

class FooCollection : List<Foo>, IList<Foo>
{
    public string Bar { get; set; }        
    //Implement IList, ICollection and IEnumerable members...
}
于 2013-01-17T16:38:45.297 に答える
0

私は最近似たようなものをテストしていて、カスタムシリアライザーを必要としない次のサンプル( @Ahmed Alejoの答えに非常に似ていますが、パブリックプロパティを必要としない)を思いつきました:

void Main()
{
    var test = new Container();
    test.Children.Add("Item 1");
    test.Children.Add("Item 2");
    test.Children.Add("Item 3");
    
    Console.WriteLine(JsonConvert.SerializeObject(test));
}

class Container {
    public CustomList Children { get; set; } = new CustomList() { Name = Guid.NewGuid().ToString() };
}

[JsonObject(MemberSerialization = MemberSerialization.Fields)]
class CustomList : IList<string>
{
    private List<string> items = new List<string>();
    private string name;

    public string this[int index] { get => items[index]; set => items[index] = value; }

    public string Name { get => name; set { name = value; } }

    public int Count => items.Count;

    public bool IsReadOnly => false;

    public void Add(string item)
    {
        items.Add(item);
    }

    public void Clear()
    {
        items.Clear();
    }

    public bool Contains(string item) => items.Contains(item);

    public void CopyTo(string[] array, int arrayIndex)
    {
        items.CopyTo(array, arrayIndex);
    }

    public IEnumerator<string> GetEnumerator() => items.GetEnumerator();

    public int IndexOf(string item) => items.IndexOf(item);

    public void Insert(int index, string item)
    {
        items.Insert(index, item);
    }

    public bool Remove(string item) => items.Remove(item);

    public void RemoveAt(int index)
    {
        items.RemoveAt(index);
    }

    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)items).GetEnumerator();
}

重要なのは、クラスに明示を追加するJsonObjectAttributeことです。これにより、配列のようにではなく、オブジェクトのように扱われます(これは、を使用して明示的に示すこともできますJsonArrayAttribute)。上記のテストの(美化された)出力は次のとおりです。

{
  "Children": {
    "items": [
      "Item 1",
      "Item 2",
      "Item 3"
    ],
    "name": "ff8768f7-5efb-4622-b7e2-f1472e80991c"
  }
}
于 2021-01-27T18:19:51.310 に答える