带有私有列表的自定义集合中的序列化问题

Serialization problem in a custom collection with a private list

给定两个类如下:

class ListCollection : List<int>
{
}

[Serializable]
class PrivateListCollection: ISerializable
{
    private List<int> list = new List<int>();

    public void Add(int value)
    {
        list.Add(value);
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("items", list);
    }
}

我在将 1,2,3 添加到它们中的每一个并使用 JsonConvert.SerializeObject 序列化后得到:

ListCollection: [1,2,3]
PrivateListCollection: {"items":[1,2,3]}

问题是是否可以得到PrivateListCollection的序列化结果为[1,2,3](没有前面的“items”之类的)?

POCO 无法通过实现 ISerializable because a JSON array is an ordered sequence of values, while SerializationInfo represents a collection of name/value pairs -- which matches exactly the definition of a JSON object rather than an array. This is confirmed by the Json.NET docs:

将自身序列化为 JSON 数组

ISerializable

Types that implement ISerializable and are marked with SerializableAttribute are serialized as JSON objects. When serializing, only the values returned from ISerializable.GetObjectData are used; members on the type are ignored. When deserializing, the constructor with a SerializationInfo and StreamingContext is called, passing the JSON object's values.

要将 PrivateListCollection 序列化为 JSON 数组,您有两种选择。

首先,你可以创建一个custom JsonConverer:

class PrivateListCollectionConverter : JsonConverter<PrivateListCollection>
{
    const string itemsName = "items";
    
    public override PrivateListCollection ReadJson(JsonReader reader, Type objectType, PrivateListCollection existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var list = serializer.Deserialize<List<int>>(reader);
        if (list == null)
            return null;
        existingValue = existingValue ?? new PrivateListCollection();
        foreach (var item in list)
            existingValue.Add(item);
        return existingValue;
    }

    public override void WriteJson(JsonWriter writer, PrivateListCollection value, JsonSerializer serializer)
    {
        // There doesn't seem to be any way to extract the items from the PrivateListCollection other than to call GetObjectData(), so let's do that.
        // Adding PrivateListCollectionConverter as a nested type inside PrivateListCollection would be another option to make the items accessible.
        ISerializable serializable = value;
        var info = new SerializationInfo(value.GetType(), new FormatterConverter());
        serializable.GetObjectData(info, serializer.Context);
        var list = info.GetValue(itemsName, typeof(IEnumerable<int>));
        serializer.Serialize(writer, list);
    }
}

然后像这样将它添加到 PrivateListCollection

[Serializable]
[JsonConverter(typeof(PrivateListCollectionConverter))]
class PrivateListCollection: ISerializable
{
    // Remainder unchanged

或像这样将其添加到 JsonSerializerSettings

var settings = new JsonSerializerSettings
{
    Converters = { new PrivateListCollectionConverter() },
};

演示 fiddle #1 here.

其次,你可以让 PrivateListCollection 实现 IEnumerable<int> 并添加一个参数化的构造函数采用单个 IEnumerable<int> 参数,如下所示:

[Serializable]
class PrivateListCollection: ISerializable, IEnumerable<int>
{
    public PrivateListCollection() { }
    public PrivateListCollection(IEnumerable<int> items) => list.AddRange(items);

    private List<int> list = new List<int>();

    public void Add(int value)
    {
        list.Add(value);
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("items", list);
    }
            
    public IEnumerator<int> GetEnumerator() => list.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

这样做后,Json.NET 会将 PrivateListCollection 视为一个 constructible read-only collection,它可以作为一个 JSON 数组往返。但是,基于你已经命名你的类型PrivateListCollection,你可能不想实现枚举。

演示 fiddle #2 here.