【问题标题】:How to deserialize nested JSON arrays with Json.Net?如何使用 Json.Net 反序列化嵌套的 JSON 数组?
【发布时间】:2021-03-12 10:57:35
【问题描述】:

我想反序列化 HTTP 请求对对象的响应。
响应如下所示:

[
    "BeginOfEnumerable",
    [
        {
            "StatusID": 12345,
            "ItemID": 987654
        }
    ],
    "EndOfEnumerable"
]

我正在使用 Newtonsoft.Json。

我尝试使用 Visual Studio 的 Edit > Paste special > Paste JSON as Classes 创建类模型,但结果对我来说看起来很奇怪:

Public Class Rootobject
        Public Property Property1() As Object
End Class

此外,这会引发异常:

Dim myObject As Rootobject = JsonConvert.DeserializeObject(response.Content)

» 解析值时遇到意外字符:.Path ", line 0,位置 0。

我想获得StatusIDItemID
我使用的 JSON Validator 说这个 JSON 是有效的。

【问题讨论】:

  • 如果我使用Dim myObject As Rootobject = JsonConvert.DeserializeObject(response.Content),我会得到一个System.InvalidCastException: Unable to cast object of type 'Newtonsoft.Json.Linq.JArray' to type 'Rootobject'.,如果我只是使用显示的 JSON 字符串执行JsonConvert.DeserializeObject(response.Content),则没有例外,请参阅dotnetfiddle.net/5j88N1。你能分享一个minimal reproducible example,或者至少是完整的ToString()异常输出,包括异常类型、消息和内部异常(如果有的话)?

标签: json vb.net visual-studio json.net deserialization


【解决方案1】:

JSON 结构本身是有效的,您可能对不遵循name:value 模式的字符串有一些困难。

您可以只反序列化值的内部数组,如 List(class),例如,

Imports System.Collections.Generic
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq

Public Class EnumObject
    Public Property StatusId As Long
    Public Property ItemId As Long
End Class

'[...]

Dim jsonEnumsTokens = JArray.Parse(response.Content)
Dim enumsArray = jsonEnumsTokens.Children.Skip(1).First().ToObject(Of List(Of EnumObject))()

它返回包含值的EnumObject 列表,如果这就是您感兴趣的全部并且您不需要以任何其他方式处理此 JSON,或以相同的形式将其序列化。

如果您只想获取数组的 JSON 表示:

Dim justTheArraysJson = jsonEnumsTokens.Children.Skip(1).First().ToString()

如果你想执行反序列化和序列化,维护结构,你可以使用一个自定义的 JsonConverter 来处理这个case

EnumObjectsConverter 创建一个 中间 结构,其中包含单个字符串和值数组,包含在 EnumObjectArray 属性中。
如果需要,这也允许序列化回原始结构。

称它为:

Dim enumArray = EnumObjectsHandler.Deserialize(response.Content)
Dim serialized = EnumObjectsHandler.Serialize(enumArray)

为序列化提供静态方法的EnumObjectsHandler 类:

Public Class EnumObjectsHandler
    Private Shared settings As JsonSerializerSettings = New JsonSerializerSettings() With {
        .Converters = {New EnumObjectsConverter()}
    }
    Public Shared Function Deserialize(json As String) As List(Of EnumObjectsRoot)
        Return JsonConvert.DeserializeObject(Of List(Of EnumObjectsRoot))(json, settings)
    End Function

    Public Shared Function Serialize(data As List(Of EnumObjectsRoot)) As String
        Return JsonConvert.SerializeObject(data, settings)
    End Function

    Public Class EnumObject
        Public Property StatusId As Long
        Public Property ItemId As Long
    End Class

    Public Structure EnumObjectsRoot
        Public Property EnumObjectArray As List(Of EnumObject)
        Public Property Operation As String

        Public Shared Widening Operator CType(objectArray As List(Of EnumObject)) As EnumObjectsRoot
            Return New EnumObjectsRoot With {.EnumObjectArray = objectArray}
        End Operator

        Public Shared Widening Operator CType(op As String) As EnumObjectsRoot
            Return New EnumObjectsRoot With {.Operation = op}
        End Operator
    End Structure


    Friend Class EnumObjectsConverter
        Inherits JsonConverter

        Public Overrides Function CanConvert(t As Type) As Boolean
            Return t Is GetType(EnumObjectsRoot)
        End Function

        Public Overrides Function ReadJson(reader As JsonReader, t As Type, existingValue As Object, serializer As JsonSerializer) As Object
            Select Case reader.TokenType
                Case JsonToken.String
                    Dim stringValue = serializer.Deserialize(Of String)(reader)
                    Return New EnumObjectsRoot() With {
                        .Operation = stringValue
                    }
                Case JsonToken.StartArray
                    Dim arrayValue = serializer.Deserialize(Of List(Of EnumObject))(reader)
                    Return New EnumObjectsRoot() With {
                        .EnumObjectArray = arrayValue
                    }
            End Select
            Throw New Exception("EnumObjectsRoot could not be deserialized")
        End Function

        Public Overrides Sub WriteJson(writer As JsonWriter, untypedValue As Object, serializer As JsonSerializer)
            Dim value = CType(untypedValue, EnumObjectsRoot)
            If value.Operation IsNot Nothing Then
                serializer.Serialize(writer, value.Operation)
                Return
            End If
            If value.EnumObjectArray IsNot Nothing Then
                serializer.Serialize(writer, value.EnumObjectArray)
                Return
            End If
            Throw New Exception("EnumObjectsRoot could not be serialized")
        End Sub
    End Class
End Class

【讨论】:

  • 与其反序列化First().ToString()返回的字符串,不如直接使用JToken.ToObject()反序列化反序列化,无需中间字符串格式化和重新解析,即jsonEnumsTokens.Children.Skip(1).First().ToObject(Of List(Of EnumObject))()
  • @dbc 当然,我已经添加了编辑。我经常在此处使用该表单发布,因为我注意到人们希望查看此时 JSON 字符串内容是什么,例如 string justTheEnumsJson = jsonEnumsTokens.Children.Skip(1).First().ToString();。我可能会放弃它,它对 GC 不友好,如果有人只是在实际代码中腼腆/粘贴它。
  • 非常感谢您的努力! :) » 问题是 HTTP-Request 的响应中前面的空格字符
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-17
  • 2020-02-25
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 2019-09-22
相关资源
最近更新 更多