我正在VB.NET.中使用以下语句将JSON反序列化为数据表
Dim _dt As DataTable = JsonConvert.DeserializeObject(Of DataTable)(myRecords)
myRecords是一个JSON字符串。
它工作正常,但myRecords有一些数字属性,如{"PhoneNo":"123456789"、"ID":"46541"},在反序列化后,这些属性将转换为数据类型为字符串的列。
如何将它们反序列化为数字?myRecords是动态填充的,所以我不能硬编码。
"PhoneNo" : "123456789"
创建字符串类型列的原因是"123456789"
实际上是一个符合Json标准的字符串文本。一个数字文字看起来是这样的,值周围没有双引号:123456789
。您确定这些属性总是为数字字符串吗?例如,并不是所有的电话号码都是数字,所以把它们硬编码成数字似乎是不明智的。
也就是说,如果您确信这些属性将始终是数字字符串,并且希望Json.NET为它们创建数字DataTable
列,则需要提前告诉它这些列所需的类型。一种选择是从适当的模式创建一个类型化的DataTable
。在这种情况下,JsonConvert.DeserializeObject(Of TTypedDataTable)(myRecords)
将创建一个具有所需列类型的DataTable
子类。
另一种选择是使用适当的列集手动创建DataTable
,然后从JSON填充表。不幸的是,JsonConvert.PopulateObject()
无法在预先分配的DataTable
上工作,因此您需要直接调用DataTableConverter.ReadJson()
。这可以通过以下扩展方法来实现:
Public Module JsonExtensions
Public Sub PopulateDataTable(json As String, target As DataTable, Optional settings As JsonSerializerSettings = Nothing)
Using reader = New JsonTextReader(New StringReader(json))
Do
If reader.TokenType = JsonToken.StartArray Then
' Populate the table
Dim converter = New DataTableConverter()
converter.ReadJson(reader, target.GetType(), target, JsonSerializer.CreateDefault(settings))
End If
Loop While reader.Read()
End Using
End Sub
End Module
然后按如下方式使用:
Dim _dt = New DataTable()
_dt.Columns.Add("PhoneNo", GetType(Long))
_dt.Columns.Add("ID", GetType(Long))
JsonExtensions.PopulateDataTable(myRecords, _dt)
小提琴示例。
你还写道,我不会硬编码如果您真的不知道哪些具有字符串值的列实际上应该反序列化为数字类型,那么您可以做的是预处理JSON,方法是将其加载到Jtoken
中,按名称对所有属性值进行分组,并为每个组检查组中的所有值是否为可转换为数字的字符串。如果全部都是可转换的,则可以进行转换。但是,如果只有一些是可转换的,则不应该进行转换,因为这将破坏Json.NET的类型推理算法。可以使用以下扩展方法完成:
Public Module JsonExtensions
Private ReadOnly NumberTypes = New JTokenType() {JTokenType.[Integer], JTokenType.Float, JTokenType.[String], JTokenType.Comment, JTokenType.Raw, JTokenType.[Boolean]}
Private Function ValidateToken(o As JToken, validTypes As JTokenType(), nullable As Boolean) As Boolean
Return (Array.IndexOf(validTypes, o.Type) <> -1) OrElse (nullable AndAlso (o.Type = JTokenType.Null OrElse o.Type = JTokenType.Undefined))
End Function
<System.Runtime.CompilerServices.Extension> _
Public Function CanConvertToNullableLong(token As JToken) As Boolean
' Reverse engineered from
' public static explicit operator long?(JToken value)
' https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/JToken.cs#L1045
If token Is Nothing OrElse token.Type = JTokenType.Null OrElse token.Type = JTokenType.Boolean Then
Return True
End If
If Not ValidateToken(token, NumberTypes, True) Then
Return False
End If
Dim jValue = TryCast(token, JValue)
If jValue Is Nothing Then
Return False
End If
If TypeOf jValue.Value Is BigInteger Then
Dim i = CType(jValue.Value, BigInteger)
Return i <= Long.MaxValue AndAlso i >= Long.MinValue
End If
Dim s = CType(jValue, String)
Dim v As Long
Return Long.TryParse(s, NumberStyles.Number, NumberFormatInfo.InvariantInfo, v)
End Function
Public Sub TryConvertColumnsToNullableLong(root As JToken)
If TypeOf root Is JContainer Then
' If ALL columns values of a given name can be converted from string to long, then do so.
' Do not convert columns where some but not all are convertable.
For Each group In DirectCast(root, JContainer) _
.Descendants() _
.OfType(Of JProperty)() _
.GroupBy(Function(p) p.Name) _
.Where(Function(g) g.All(Function(p) (p.Value.Type = JTokenType.String Or p.Value.Type = JTokenType.Null) AndAlso p.Value.CanConvertToNullableLong()))
For Each p In group
p.Value = CType(p.Value, System.Nullable(Of Long))
Next
Next
End If
End Sub
End Module
然后进行如下预处理和反序列化:
Dim token = JToken.Parse(myRecords)
JsonExtensions.TryConvertColumnsToNullableLong(token)
Dim _dt = token.ToObject(Of DataTable)()
小提琴#2示例。
不过,我不确定我会推荐这个。JSON值是字符串这一事实表明,在生成JSON的数据库中,这些值可能是任意字符串。如果是这样的话,当非数字值开始输入数据库时,对long
的转换进行黑客攻击可能会在以后引发问题。