使用 JsonConvert 将 JSON 反序列化为 VB.NET 中的数据表

Posted

技术标签:

【中文标题】使用 JsonConvert 将 JSON 反序列化为 VB.NET 中的数据表【英文标题】:Deserialize JSON into data table in VB.NET using JsonConvert 【发布时间】:2017-01-19 18:10:56 【问题描述】:

我正在使用 VB.NET 中的以下语句将 JSON 反序列化为数据表。

Dim _dt As DataTable = JsonConvert.DeserializeObject(Of DataTable)(myRecords)

myRecords 是一个 JSON 字符串。

它工作正常,但 myRecords 有一些数字属性,如 "PhoneNo":"123456789", "ID":"46541" 并且在反序列化后,这些属性将转换为数据类型为字符串的列。

如何将它们反序列化为数字? myRecords 正在动态填充,所以我不能硬编码。

【问题讨论】:

json 是从哪里来的?它是什么样子的?通常电话号码字符串:您不能添加或减去它们,因此它们不是数字。如果 ID 被序列化为字符串,你可能需要编写一个转换器来强制它为 int 【参考方案1】:

Json.NET 为"PhoneNo" : "123456789" 创建字符串类型列的原因是"123456789" 实际上是根据JSON standard 的字符串文字。数字文字看起来像这样,值周围没有双引号:123456789。您确定这些属性将始终是数字字符串吗?例如,并非所有电话号码都是数字,因此硬编码它们似乎是不明智的。

话虽如此,如果您确定这些属性将始终是数字字符串并希望 Json.NET 为它们创建数字 DataTable 列,则需要提前告诉它所需的这些列的类型。一种选择是从适当的模式创建typed 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)

例如fiddle。

你也写了,我不能硬编码。如果你真的不知道哪些具有字符串值的列实际上应该反序列化为数字类型,你可以做的是预先- 通过将 JSON 加载到 Jtoken 来处理 JSON,按名称对所有属性值进行分组,并为每个组检查 所有 组中的值是否是可转换为数字的字符串。如果都是可转换的,您可以进行转换。但是如果 只有一些 是可转换的,则不应进行转换,因为这会破坏 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)()

例如fiddle #2。

我不确定我是否会推荐这个。 JSON 值是字符串这一事实表明,在生成 JSON 的数据库中,这些值可能是任意字符串。如果是这样,当非数字值开始输入数据库时​​,破解到long 的转换可能会导致问题。

【讨论】:

以上是关于使用 JsonConvert 将 JSON 反序列化为 VB.NET 中的数据表的主要内容,如果未能解决你的问题,请参考以下文章

将json字符串反序列化为DataTable 对JsonConvert的补充

检测反序列化的对象是不是缺少 Json.NET 中 JsonConvert 类的字段

c# 多个json字符串反序列化

Json/XML序列化和反序列化

Newtonsoft.Json.JsonConvert 从同一个类中序列化和反序列化

Newtonsoft.Json.JsonConvert 序列化与反序列化