我需要反序列化原始二进制数据(BinaryFormatter),然后序列化为JSON(用于编辑),然后再次将其序列化回二进制。
显然,我在彩车上输了。当我从原始序列进行序列化时,原始浮点值0xF9FF4FC1
(大端,大约 -12.9999933 )被四舍五入为0xF6FF4FC1
( -12.99999 )二进制(正确的数据和中间数据在内存中为1:1)转换为 JSON 。我知道损失不大,而且我知道浮动有问题,但由于以后可能会出现不兼容问题,我想使精度尽可能接近。
有人用JSON处理过此问题吗?如何强制它以最大精度写入浮点数?我尝试使用内置选项将浮点数处理为十进制或双精度,但是不幸的是,输出没有区别,并且我无法更改目标值,因为在执行二进制序列化时仍需要将它们写为浮点数,因此会四舍五入不管在隐式转换期间。
我要往返的包含浮点数的特定类型是https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs中的Vector2
。
tl:dr有一个浮点数,希望JsonNET将其尽可能精确地序列化为最终的json字符串。
P.S。我在这里读过很多问题,在其他地方也读过博客条目,但是没有找到任何人试图解决同一问题,大多数搜索命中都涉及浮动阅读问题(稍后我也需要解决)。
更新: 正如下面的@dbc所指出的那样-Jsont.NET尊重“ TypeConverter”属性,因此我必须创建自己的转换器来覆盖它。
答案 0 :(得分:2)
Json.NET将使用往返精度格式float
(source)来序列化"R"
值。但是,您使用的类型https://github.com/FNA-XNA/FNA/blob/master/src/Vector2.cs已应用TypeConverter
:
[Serializable]
[TypeConverter(typeof(Vector2Converter))]
[DebuggerDisplay("{DebugDisplayString,nq}")]
public struct Vector2 : IEquatable<Vector2>
{
//...
如Newtonsoft docs中所述,此类类型将使用转换器序列化为字符串:
原始类型
.Net:
TypeConverter
(可转换为字符串)
JSON:字符串
然后,检查https://github.com/FNA-XNA/FNA/blob/master/src/Design/Vector2Converter.cs的代码,发现在around line 60处转换为字符串时,此转换器不使用往返精度格式:
return string.Join(
culture.TextInfo.ListSeparator,
new string[]
{
vec.X.ToString(culture),
vec.Y.ToString(culture)
}
);
因此内置TypeConverter
本身就是您失去精度的地方。
为避免此问题,您可以
为Vector2
创建custom JsonConverter
,如下所示:
public class Vector2Converter : JsonConverter
{
class Vector2DTO
{
public float X;
public float Y;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Vector2) || objectType == typeof(Vector2?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// A JSON object is an unordered set of name/value pairs so the converter should handle
// the X and Y properties in any order.
var dto = serializer.Deserialize<Vector2DTO>(reader);
if (dto == null)
return null;
return new Vector2(dto.X, dto.Y);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var vec = (Vector2)value;
serializer.Serialize(writer, new Vector2DTO { X = vec.X, Y = vec.Y });
}
}
如果将转换器添加到JsonSerializerSettings.Converters
,它将取代TypeConverter
。
工作示例小提琴here。
转换器将Vector2
序列化为具有X
和Y
属性的对象,但是您也可以根据需要将其序列化为具有两个值的数组。我不建议将序列化为原语string
,因为Vector2
不对应于JSON原语。
使用不会为应用了TypeConverter
的类型创建原始协定的custom contract resolver,例如在Newtonsoft.JSON cannot convert model with TypeConverter attribute的答案中所示的类型。
注意:
往返格式"R"
显然不能保留+0.0
和-0.0
之间的差异,因此Json.NET也没有,请参见https://dotnetfiddle.net/aereJ2或https://dotnetfiddle.net/DwoGyX进行演示。 Microsoft docs没有提到使用这种格式时是否应保留零号。
因此,在这种情况下,往返float
可能会导致二进制更改。
顺便说一句,Json.NET在编写"R"
时也使用double
(如source for JsonConvert.ToString(double value, ...)
所示):
internal static string ToString(double value, FloatFormatHandling floatFormatHandling, char quoteChar, bool nullable)
{
return EnsureFloatFormat(value, EnsureDecimalPlace(value, value.ToString("R", CultureInfo.InvariantCulture)), floatFormatHandling, quoteChar, nullable);
}
仅对 double
记录,此格式可能会失去精度。来自The Round-trip ("R") Format Specifier:
在某些情况下,如果使用
/platform:x64
或/platform:anycpu
开关编译并在64位系统上运行,则用“ R”标准数字格式字符串格式化的Double值不能成功往返。
因此,通过Json.NET来回double
可能会导致较小的二进制差异。但是,这并不严格适用于这里的问题,特别是关于float
的问题。