我一直在努力使用Utf8JsonWriter编写诸如82.0
这样的双精度浮点数。
默认情况下,WriteNumberValue
方法接受一个双精度浮点数并为我格式化它,而格式(标准的'G'格式)省略了“.0”后缀。我找不到控制它的方法。
从设计上看,我似乎不能只向Utf8JsonWriter写入一个原始字符串,但我找到了一个变通办法:创建一个JsonElement并调用JsonElement.WriteTo '。这将调用Utf8JsonWriter中的一个私有方法并将字符串直接写入其中。
有了这个发现,我做了一个感觉非常笨拙和低效的实现。
open System.Text.Json
void writeFloat(Utf8JsonWriter w, double d) {
String floatStr = f.ToString("0.0################")
JsonElement jse = JsonDocument.Parse(floatStr).RootElement
jse.WriteTo(w)
}
我需要格式化一个double,所以这没什么,但是解析它,创建一个jsonDocument和一个JsonElement,仅仅是为了能够找到一种方法来调用一个受保护的方法,看起来真的很浪费。但是,它确实工作了(我用F#写的,然后翻译成C#,如果我在语法上犯了错误,请道歉)。
是否有更好的方法?我想到了一些可能的解决方案(我是dotnet的新手,所以不确定这里有什么可能):
- 有没有直接访问私有API的方法?我想子类化Utf8Writer可能会工作,但它是一个密封类。
- 我是否可以直接示例化JsonElement而无需整个冗长的解析过程?
至于为什么这样做是必要的:我需要强制整数值附加.0
,因为我需要与一种非常特殊的格式交互,它区分整数和浮点JSON值。(我可以使用指数格式,因为它显然是浮点数)。
1条答案
按热度按时间qoefvg9y1#
您的要求是创建满足以下条件的
JsonConverter<double>
:double
值时,如果该值是整数,则必须附加.0
小数部分。double.PositiveInfinity
)时没有更改。JsonNumberHandling
选项WriteAsString
或AllowReadingFromString
。JsonDocument
的中间分析。在**.NET 6和更高版本**中,您可以手动设置双精度浮点数的格式,并使用
Utf8JsonWriter.WriteRawValue()
将其写出。以下转换器根据需要起作用:备注:
Utf8Formatter
将该值格式化为utf8 stackalloc'edbyte
数组,然后根据需要添加.0
,最后使用skipInputValidation = true
写入。这样做应该可以获得最佳性能,因为Utf8JsonWriter
被设计为直接写入utf8缓冲区或流,而不是写入utf 16文本写入器,后者的内容随后被编码为utf8。Utf8Formatter
会产生区域设置不变的输出,但如果您使用ToString()
方法(例如f.ToString("0.0################")
),请务必在区域设置不变的情况下执行此操作,如下所示:这保证了即使在使用逗号的语言环境中也会使用正确的JSON小数分隔符
.
。double.IsFinite(value)
检查旨在正确序列化非有限值(如double.PositiveInfinity
)。通过实验,我发现Utf8JsonWriter.WriteNumberValue(value)
会无条件地抛出这些类型的值,因此在启用JsonNumberHandling.AllowNamedFloatingPointLiterals
时,必须调用序列化程序来正确处理它们。演示小提琴#1
在**.NET 5和更早版本中**
Utf8JsonWriter.WriteRawValue()
不存在,因此,正如mjwills在注解中所建议的,您可以将double
转换为带有所需小数部分的decimal
,然后将其写入JSON,如下所示:备注:
decimal
(与double
不同)保留尾随零,如文档备注中所述。double
强制转换为decimal
可以将lose precision转换为大值,因此只需执行不建议强制使用最小位数。(例如,序列化
9999999999999992
将导致9999999999999990.0
而不是9999999999999992.0
。)然而,根据Wikipedia页面的双精度浮点格式:整数值的精度限制,从-2^53到2^53的整数可以精确地表示为
double
,因此转换为十进制并强制最小位数可以用于该范围内的值。decimal
之外,没有办法在运行时直接设置它的位数。为了提高性能,我使用Utf8Formatter
和Utf8Parser
,但是在. NETCore3.0之前的框架中,这可能会失去精度,而应该使用常规的string
和解析。有关详细信息,请参阅Utf8JsonWriter.WriteValues.Double.cs
的代码注解。您可以使用反射来调用私有方法,如 * How do I use reflection to invoke a private method? * 所示,但是不建议这样做,因为内部方法可以随时更改,从而破坏您的实现。除此之外,没有公共API可以直接写入“原始”JSON,除非将其解析为
JsonDocument
,然后写入。我不得不在my answer中使用相同的技巧来写入 * Serialising BigInteger using System.Text.Json *。在.NET 5中,这是不可能的。如source code中所示,
JsonElement
结构只包含对其父级JsonDocument _parent
的引用,以及指示元素在文档中位置的位置索引。事实上,在.NET 5中,当您使用
JsonSerializer.Deserialize<JsonElement>(string)
反序列化为JsonElement
时,JsonElementConverter
会在内部将传入的JSON读取到临时JsonDocument
中,克隆其RootElement
,然后处置文档并返回克隆。value < MaxPreciselyRepresentedIntValue
的特殊情况旨在尽可能避免任何到文本表示的往返,从而最大限度地提高性能。我实际上还没有配置文件来确认这比做一个文本往返更快。
演示fiddle #2 here,其中包括一些单元测试,这些单元测试Assert转换器对于大范围的整数
double
值生成与Json.NET相同的输出,因为Json.NET在序列化这些值时总是附加一个.0
。