如何使用 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
。有两种可能:
作为 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
]
}
作为 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://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.
我正在尝试使用 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
。有两种可能:
作为
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 ] }
作为
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://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.