首页 > 解决方案 > 如何使用 JsonSerializer 反序列化 BitArray?

问题描述

我正在尝试使用 Json.Net 将 Json 文件的对象序列化和反序列化到字典。序列化效果很好,我可以看到文件中的所有数据。但是当我尝试反序列化时,它无法填充 System.Collections.BitArray。是否不正确支持 BitArrays?

Json 文件似乎具有正确的值和正确的格式。我还单步执行了代码,它正确构建了对象,只是未能设置 BitArray 的值。到目前为止,它对所有对象都正常工作,只有在我引入一个带有 BitArray 的对象时才会失败。

失败的对象

    [DataContract]
    public class Chip
    {
        [DataMember]
        public Guid ID { get; set; }

        [DataMember]
        public BitArray Input { get; set; } //Failing on setting this value
        [DataMember]
        public BitArray Output { get; set; }

        [DataMember]
        public List<Gate> Gates { get; set; }
        [DataMember]
        public List<Chip> Chips { get; set; }
        [DataMember]
        public Dictionary<Guid, List<Wire>> WireDict { get; set; }

        [DataMember]
        protected BitArray Dirty { get; set; }

        protected Chip(int inputs, int outputs)
        {
            ID = Guid.NewGuid();

            Input = new BitArray(inputs, false);
            Output = new BitArray(outputs, false);
            Dirty = new BitArray(outputs, false);

            Gates = new List<Gate>();
            Chips = new List<Chip>();
            WireDict = new Dictionary<Guid, List<Wire>>();
        }
    }

我用来序列化的代码

using(StreamWriter file = File.CreateText(filePath))
{
    JsonSerializer serializer = new JsonSerializer
    {
        TypeNameHandling = TypeNameHandling.Auto,
        Formatting = Formatting.Indented
    };            

    serializer.Serialize(file, componentsDict);
}

我用来反序列化的代码

using (StreamReader file = File.OpenText(filePath))
{
    JsonSerializer serializer = new JsonSerializer();
    serializer.TypeNameHandling = TypeNameHandling.Auto;
    Dictionary<Guid, ChipWrapper> componentsDict = (Dictionary<Guid, ChipWrapper>)serializer.Deserialize(file, typeof(Dictionary<Guid, ChipWrapper>));
}

我得到错误

JsonSerializationException: Cannot populate list type System.Collections.BitArray. Path 'a77af562-0e5e-4471-86c5-06857610ae6d.Chip.Input', line 612, position 16.
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.Object existingValue, System.String id) (at <97722d3abc9f4cf69f9e21e6770081b3>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, 

ETC...

Dictionary 包含一个类,许多其他类都派生自该类,但只有具有位数组的类失败。

标签: c#serializationjson.netdeserializationbitarray

解决方案


你是对的,Json.NET 不能序列化BitArray,特别是因为这个类是一个从.Net 1.1开始的无类型集合

public sealed class BitArray : ICloneable, System.Collections.ICollection

由于该类仅实现ICollection而不是ICollection<bool>,因此 Json.NET 不知道将其成员反序列化到的正确类型,也不知道一旦创建后如何将它们添加到集合中。

解决此问题的最简单方法是为此类型创建自定义 JsonConverter 。但首先,我们需要选择如何BitArray在 JSON 中表示。有两种可能:

  1. 作为一个bool值数组。这种表示很容易使用,但在序列化时会占用大量空间。

    JsonConverter以这种格式生成 JSON 的 A 如下所示:

    public class BitArrayConverter : JsonConverter<BitArray>
    {
        public override BitArray ReadJson(JsonReader reader, Type objectType, BitArray existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            var bools = serializer.Deserialize<bool[]>(reader);
            return bools == null ? null : new BitArray(bools);
        }
    
        public override void WriteJson(JsonWriter writer, BitArray value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value.Cast<bool>());
        }
    }
    

    使用相应的 JSON:

    {
      "ID": "4fa76f3a-66fc-4832-8c94-280725486270",
      "Input": [
        true,
        true,
        false
      ],
      "Output": [
        true,
        true,
        false
      ],
      "Dirty": [
        false,
        false,
        false
      ]
    }
    
  2. 作为01字符的字符串,例如"110". 这将比上面的数组更紧凑,但仍然应该很容易使用:

    public class BitArrayConverter : JsonConverter<BitArray>
    {
        public override BitArray ReadJson(JsonReader reader, Type objectType, BitArray existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            else if (reader.TokenType != JsonToken.String)
                throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
            var s = (string)reader.Value;
            var bitArray = new BitArray(s.Length);
            for (int i = 0; i < s.Length; i++)
                bitArray[i] = s[i] == '0' ? false : s[i] == '1' ? true : throw new JsonSerializationException(string.Format("Unknown bit value {0}", s[i]));
            return bitArray;
        }
    
        public override void WriteJson(JsonWriter writer, BitArray value, JsonSerializer serializer)
        {
            writer.WriteValue(value.Cast<bool>().Aggregate(new StringBuilder(value.Length), (sb, b) => sb.Append(b ? "1" : "0")).ToString());
        }
    }
    

    使用相应的 JSON:

    {
      "ID": "4fa76f3a-66fc-4832-8c94-280725486270",
      "Input": "110",
      "Output": "110",
      "Dirty": "000"
    }
    
  3. 作为包含byte []数组以及位数的 DTO。这种表示可能不太容易使用,但对于大型数组来说最紧凑,因为 Json.NET 会自动在 Base64 中编码字节数组。

    这种格式的转换器如下所示:

    public class BitArrayConverter : JsonConverter<BitArray>
    {
        class BitArrayDTO
        {
            public byte[] Bytes { get; set; }
            public int Length { get; set; }
        }
    
        public override BitArray ReadJson(JsonReader reader, Type objectType, BitArray existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            var dto = serializer.Deserialize<BitArrayDTO>(reader);
            if (dto == null)
                return null;
            var bitArray = new BitArray(dto.Bytes);
            bitArray.Length = dto.Length;
            return bitArray;
        }
    
        public override void WriteJson(JsonWriter writer, BitArray value, JsonSerializer serializer)
        {
            var dto = new BitArrayDTO
            {
                Bytes = value.BitArrayToByteArray(),
                Length = value.Length
            };
           serializer.Serialize(writer, dto);
        }
    }
    
    public static class BitArrayExtensions
    {
        // Copied from this answer https://stackoverflow.com/a/4619295
        // To https://stackoverflow.com/questions/560123/convert-from-bitarray-to-byte
        // By https://stackoverflow.com/users/313088/tedd-hansen
        // And made an extension method.
        public static byte[] BitArrayToByteArray(this BitArray bits)
        {
            byte[] ret = new byte[(bits.Length - 1) / 8 + 1];
            bits.CopyTo(ret, 0);
            return ret;
        }
    }
    

    使用相应的 JSON:

    {
      "ID": "4fa76f3a-66fc-4832-8c94-280725486270",
      "Input": {
        "Bytes": "Aw==",
        "Length": 3
      },
      "Output": {
        "Bytes": "Aw==",
        "Length": 3
      },
      "Dirty": {
        "Bytes": "AA==",
        "Length": 3
      }
    }
    

现在,Chip您的问题中显示的类型还有一个问题,即它没有公共构造函数(甚至没有私有默认构造函数)。因此,Json.NET 将不知道如何构建它。这可能是您的问题中的一个错字,但如果不是,您还需要一个JsonConverterfor Chip,特别是一个继承自CustomCreationConverter<T>

public class ChipConverter : CustomCreationConverter<Chip>
{
    public override bool CanConvert(Type objectType) { return objectType == typeof(Chip); }

    public override Chip Create(Type objectType)
    {
        return (Chip)Activator.CreateInstance(typeof(Chip), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { 0, 0 }, null);
    }
}

拥有所有必要的转换器后,您可以按如下方式序列化和反序列化:

var settings = new JsonSerializerSettings
{
    Converters = { new ChipConverter(), new BitArrayConverter() },
};
var chipJson = JsonConvert.SerializeObject(chip, Formatting.Indented, settings);
var chip2 = JsonConvert.DeserializeObject<Chip>(chipJson, settings);

通过在设置中添加转换器,无需对Chip数据模型进行任何更改。

演示小提琴在这里


推荐阅读