ServiceStack 文本设置以在反序列化 json 时推断原始值类型
Posted
技术标签:
【中文标题】ServiceStack 文本设置以在反序列化 json 时推断原始值类型【英文标题】:ServiceStack Text setting to infer primitive values types while deserializing json 【发布时间】:2014-10-07 01:49:20 【问题描述】:我有一个在运行时生成的带有 x 个属性的 json,因此我不能使用 POCO 来反序列化它,例如
""UserId": 1234,"Name": "Adnan","Age": 30, "Salary": 3500.65"
对我来说最好的选择是在Dictionary<string,object>
中反序列化它
我正在使用 ServiceStack JsonSerializer
将 json 反序列化为 Dictionary<string,object>
,这可以正常工作,但是当我尝试获取字典中对象的类型时,它们不会相应地匹配。
我尝试了以下方法:
如果我不使用以下选项,所有字典对象值都会被推断为strings
JsConfig.ConvertObjectTypesIntoStringDictionary = true;
JsConfig.TryToParsePrimitiveTypeValues = true;
当我使用上述选项时,所有Int64
和Double
值都被推断为Decimal
值。
是否有任何选项可以更改此设置,以便将原始值推断为Int64
或Double
而不是Decimal
Ps:我不要求它必须是精确类型,即Int32
,如果它属于该括号。
我尝试过使用 Json.Net,它工作正常,对象值被推断为 Int64
和 Double
,但是当我在我的项目中使用 ServiceStack JsonSerializer
时,很高兴知道这是怎么回事使用它来实现。
【问题讨论】:
【参考方案1】:设置TryToParseNumericType = true
如果您希望 ServiceStack.Text 确定 decimal
类型之外的类型,则需要设置 JsConfig.TryToParseNumericType = true
。
控制原始类型:
关于控制您的数字被解析到的类型,我提交了更改以改进 ServiceStack.Text,以提供将包含在即将发布的版本中的此类功能,with this commit。
所以你将能够做到:
JsConfig.TryParseNumericType = true;
JsConfig.ParsePrimitiveFloatingPointTypes = ParseAsType.Single;
JsConfig.ParsePrimitiveIntegerTypes = ParseAsType.Int32 | ParseAsType.Int64;
该配置将返回 Int32
而不是 byte
,正如您所指出的那样,这是一个问题。而不是decimal
,你会得到float
。
供参考:
以下是更新后的原始解析方法,现在应该提供更好的控制,以及在无法解析类型时更好的回退选项。
public static object ParsePrimitive(string value)
if (string.IsNullOrEmpty(value)) return null;
bool boolValue;
if (bool.TryParse(value, out boolValue)) return boolValue;
// Parse as decimal
decimal decimalValue;
var acceptDecimal = JsConfig.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Decimal);
var hasDecimal = decimal.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out decimalValue);
// Check if the number is an Primitive Integer type given that we have a decimal
if(hasDecimal && decimalValue == decimal.Truncate(decimalValue))
// Value is a whole number
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.Byte) && decimalValue <= byte.MaxValue && decimalValue >= byte.MinValue) return (byte)decimalValue;
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.SByte) && decimalValue <= sbyte.MaxValue && decimalValue >= sbyte.MinValue) return (sbyte)decimalValue;
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.Int16) && decimalValue <= Int16.MaxValue && decimalValue >= Int16.MinValue) return (Int16)decimalValue;
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.UInt16) && decimalValue <= UInt16.MaxValue && decimalValue >= UInt16.MinValue) return (UInt16)decimalValue;
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.Int32) && decimalValue <= Int32.MaxValue && decimalValue >= Int32.MinValue) return (Int32)decimalValue;
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.UInt32) && decimalValue <= UInt32.MaxValue && decimalValue >= UInt32.MinValue) return (UInt32)decimalValue;
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.Int64) && decimalValue <= Int64.MaxValue && decimalValue >= Int64.MinValue) return (Int64)decimalValue;
if (JsConfig.ParsePrimitiveIntegerTypes.HasFlag(ParseAsType.UInt64) && decimalValue <= UInt64.MaxValue && decimalValue >= UInt64.MinValue) return (UInt64)decimalValue;
return null;
// Value is a floating point number
// Return a decimal if the user accepts a decimal
if(hasDecimal && acceptDecimal)
return decimalValue;
// Parse as double if decimal failed or user wants a double
double doubleValue = 0;
var acceptDouble = JsConfig.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Double);
var hasDouble = (!hasDecimal || acceptDouble) && double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue);
// Return a double if the user accepts a double
if(acceptDouble && hasDouble)
return doubleValue;
// Parse as float
float floatValue;
var acceptFloat = JsConfig.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Single);
var hasFloat = float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out floatValue);
// Return a float if the user accepts a float
if(acceptFloat && hasFloat)
return floatValue;
// Default to decimal, then double , then float or null
if(hasDecimal) return decimalValue;
if(hasDouble) return doubleValue;
if(hasFloat) return floatValue;
return null;
【讨论】:
感谢 Scott,当我使用上述选项时,它似乎适用于Int
值,但对于 Float
和 Double
值,它仍将其推断为 Decimal
。跨度>
@adnangohar 是的,我认为这种解析方法可能有点缺陷,因为大多数float
s 和double
作为字符串可以解析为decimal
,但反之则不行。所以检查的顺序让它在这里。我想这将是一个需要解决的错误。
@adnangohar 我已经向 ServiceStack 团队询问过这个问题。
感谢您的帮助。我注意到的另一件事是,如果我使用"UserId": 123
,它会被解析为Byte
,并且随着int 值的增加,它开始被解析为更大的Int
值。如果我们可以有一些设置,我们可以规定整数值应该被推断为 Int64
或 Int32
并且对于 Float
和 Double
值类似。
@adnangohar 我与 ServiceStack 的作者交谈过,他说这段代码是由贡献者编写的,无论如何,他同意浮点数和双精度数的测试需要在小数检查之前进行。但普遍的共识是浮点是讨厌的:/ It's been fixed in this commit..以上是关于ServiceStack 文本设置以在反序列化 json 时推断原始值类型的主要内容,如果未能解决你的问题,请参考以下文章
用于在反序列化期间忽略未知属性的 SpringMVC 全局设置
JsonSerializer 在反序列化期间不使用内部构造函数