c# - 如何使用 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 包含一个类,许多其他类都派生自该类,但只有具有位数组的类失败。
解决方案
你是对的,Json.NET 不能序列化BitArray
,特别是因为这个类是一个从.Net 1.1开始的无类型集合:
public sealed class BitArray : ICloneable, System.Collections.ICollection
由于该类仅实现ICollection
而不是ICollection<bool>
,因此 Json.NET 不知道将其成员反序列化到的正确类型,也不知道一旦创建后如何将它们添加到集合中。
解决此问题的最简单方法是为此类型创建自定义 JsonConverter 。但首先,我们需要选择如何BitArray
在 JSON 中表示。有两种可能:
作为一个
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 ] }
作为
0
和1
字符的字符串,例如"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" }
作为包含
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 将不知道如何构建它。这可能是您的问题中的一个错字,但如果不是,您还需要一个JsonConverter
for 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
数据模型进行任何更改。
演示小提琴在这里。
推荐阅读
- asp.net-mvc - 从 DropDownListFor 检索值的问题
- javascript - Jquery 数据选择器:“无法读取 null 的属性 'setDate'”
- php - 无法在 laravel 5.6 电子邮件发送中与主机 smtp.gmail.com [连接被拒绝 #111] 建立连接
- javascript - 使用 IIFE 时抛出“`TypeError`: `[0,1]` is not a function”
- java - 通过 jdbc 运行 shell 命令
- regex - Notepad++ 正则表达式替换为同一文件中的捕获
- uicollectionview - 集合视图中的自定义布局
- python - sqlalchemy 如何在没有外键的情况下访问 Joined Model 属性?
- python - 在评论中添加招摇的描述
- python - 在本地服务器上无头运行 Python