如何使用 JsonSerializer 反序列化 BitArray?

How to deserialize BitArray using JsonSerializer?

我正在尝试使用 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, 

等等...

字典包含一个 class,许多其他 classes 都派生自它,但只有带位数组的 classes 失败了。

不确定为什么不能直接反序列化,但我可以通过创建一个 'fake' 布尔数组来绕过它,该数组充当实际 BitArray 的接口。唯一一次访问 bool 数组是在序列化时,所以它只被调用了几次并允许我继续使用 BitArray 对象。

        public BitArray Input { get; set; }

        [DataMember]
        private bool[] _Input {
            get {
                bool[] b = new bool[Input.Length];
                Input.CopyTo(b, 0);
                return b;
            }
            set
            {
                Input = new BitArray(value);
            }
        }    

你是对的,Json.NET不能序列化BitArray, specifically because this class is an untyped collection dating from .Net 1.1:

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

由于 class 仅实现 ICollection 而不是 ICollection<bool>,Json.NET 不知道反序列化其成员的正确类型,也不知道如何添加它们创建后 collection。

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

  1. 作为 bool 个值的数组。这种表示易于使用,但在序列化时会消耗大量 space。

    以这种格式生成 JSON 的 JsonConverter 看起来像:

    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://whosebug.com/a/4619295
        // To https://whosebug.com/questions/560123/convert-from-bitarray-to-byte
        // By https://whosebug.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 类型还有一个问题,即它没有 public 构造函数(甚至没有私有默认构造函数)。因此,Json.NET 将不知道如何构造它。这个 可能 是你问题中的一个错字,但如果不是,你还需要一个 JsonConverter 用于 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 数据模型进行任何更改。

演示 fiddle here.