如何将JSON文本转换为保留double与decimal精确值的JToken?
我需要将序列化的JSON文本转换为JToken实例jt,要求同时保留double和decimal的精确值:当使用jt.ToObject<T>()转换为可能包含多个double和decimal实例的类型T时,从double.MaxValue、decimal.MinValue、double.Epsilon等序列化而来的值需被精确还原(在需要使用JToken实例jt的中间环节,类型T是未知的)。
但JsonTextReader.ParseReadNumber()无法轻松实现这一点:
- 默认模式下,所有解析的浮点值都会被存储为
double类型。 - 若设置
FloatParseHandling.Decimal,则会始终存储为decimal类型。
这两种模式在读取另一种数值类型的极值(比如double.MaxValue或decimal.MinValue)时都会失败。
另外,JsonTextReader.ParseReadNumber()在解析numberType为JsonToken.Float的存储令牌(调用SetToken())时,不会保留原始源文本的副本(_stringReference),因此任何JsonConverter.ReadJson()都无法访问令牌的精确原始源文本。
我目前找到一种比较繁琐的解决方法:重写JsonTextReader.Read()方法。该方法仍会调用基类的JsonTextReader.Read()实现,但调用前会用反射记录当前流位置;调用base.Read()后,如果TokenType是数值类型,就利用读取后的流位置提取令牌的原始文本,再用该文本修正存储的令牌,使其包含能精确表示该字符串的数值类型对象(如果可行):
public override bool Read() { int? tokenSourceStartPos = GetCharPos(); bool success = base.Read(); switch (TokenType) { case JsonToken.Integer: ImproveIntegerTokenPrecision(tokenSourceStartPos); break; case JsonToken.Float: ImproveFloatingPointTokenPrecision(tokenSourceStartPos); break; } return success; }
通过继承JsonTextReader实现的SmartNumberJsonTextReader类,可以生成包含能精确表示double和decimal值的JToken的JObject。
想问是否存在更简洁的方法,能从JSON文本获取这类精确的JToken?(整数也存在类似问题,它们可能被表示为long、ulong、decimal甚至BigInt,如果能以类似方式解决就更好了。)
有两种更简洁的思路可以实现需求:
1. 自定义数值解析逻辑,拦截令牌创建
可以在读取JSON时直接捕获数值的原始字符串,根据字符串特征选择最合适的数值类型存储到JToken中,无需重写整个Read方法:
- 读取JSON时,拦截每个数值令牌的创建过程,保留原始字符串。
- 对浮点数值:先尝试解析为
decimal,若超出decimal范围再解析为double;对整数值:依次尝试int、long、ulong、BigInteger,选择能精确表示的最小类型存储。
这种方式无需依赖反射操作流位置,直接在令牌生成阶段完成类型选择,逻辑更清晰。
2. 延迟解析:以字符串形式暂存数值
如果中间环节不需要直接操作数值,仅在最终转换为T时需要精确值,可以将所有数值先以字符串形式存储在JToken中,等到调用ToObject<T>时再根据目标类型解析:
- 自定义
JsonConverter,将所有数值令牌转换为JValue类型的字符串存储。 - 扩展
ToObject<T>方法或创建专属转换器,在最终转换时,根据目标字段/属性的类型,将字符串精确解析为对应的double、decimal或整数类型。
这种方式避免了在JToken阶段提前确定数值类型,完全延迟到最终转换时处理,代码实现更简洁,也能保证精确性。
针对整数的精确存储问题,同样可以套用上述思路:要么在令牌创建阶段根据原始字符串选择合适的整数类型,要么延迟到最终转换时解析。
内容的提问来源于stack exchange,提问作者Carsten Scholtes

