【问题标题】:Deserialize JSON string which is array and has illegal property names反序列化 JSON 字符串,该字符串是数组且具有非法属性名称
【发布时间】:2026-01-09 14:15:01
【问题描述】:

我正在使用 JSON.NET 反序列化来自 HTTP 查询的 JSON 响应,但我在反序列化时遇到了问题。

源JSON是这样的:

[
  {
    "type": "rpc",
    "tid": 18,
    "action": "TaskSystem",
    "method": "createTask",
    "result": {
      "0": {
        "success": true,
        "successes": [
          [
            "Task successfuly created with S/N #22920"
          ]
        ]
      },
      "1": {
        "success": true,
        "successes": [
          [
            "Task successfuly created with S/N #22921"
          ],
          "Task #22921 marked as urgent"
        ]
      },
      "records": [
        {
          "id": 22920
        },
        {
          "id": 22921
        }
      ],
      "success": true
    }
  }
]

我一直在使用这些类进行反序列化:

Private Sub Deserialize()
    Dim Jobj = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Response())(Jstring)
End Sub

Public Class Record
    Public Property id As Integer
End Class

Public Class Result
    Public Property records As Record()
    Public Property success As Boolean
End Class

Public Class Response
    Public Property type As String
    Public Property tid As Integer
    Public Property action As String
    Public Property method As String
    Public Property result As Result
End Class

但是我丢失了查询返回的成功/失败消息

我应该如何编写类 Result 以收集属性 records As Record()succes As Boolean还有那些名为“0”、“1”等的对象......?

非常感谢您的帮助。

【问题讨论】:

  • [ "Task successfuly ... #22920" ] 后面缺少逗号,这导致了List(Of Object) 版本。你能检查一下是正确的吗?
  • 我检查了我的日志,在 #22920 的情况下没有逗号。如果不美化,那部分 JSON 字符串是"successes": [ [ "Task successfuly created with S/N #22920" ] ],这很奇怪。但话又说回来,如果这些反应真的很好,我想我不会有这些问题......
  • 哈哈。您可能想要创建一个转换器并强制这两个家伙使用相同的类型,否则答案有一个快速而肮脏的解决方法
  • 反过来呢:22921有逗号吗?
  • 是的 :-( ...我如何/在哪里可以学习编写自定义转换器以处理此类问题的基础知识?

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


【解决方案1】:

您可以将 JSON 复制到剪贴板并使用
编辑 -> 选择性粘贴 -> 将 JSON 粘贴为类
让课程变得粗糙。所有的机器人都会出错,例如:

' wrong
Public Property successes()() As String
' correct:
Public Property successes As String()()

这似乎有效:

Public Class StatusS          ' string()() version
    Public Property success As Boolean
    Public Property successes As List(Of List(Of String))
End Class

Public Class StatusO         ' Object() version
    Public Property success As Boolean
    Public Property successes As List(Of Object)
End Class

机器人也很难处理非法的属性名称,尤其是在 VB 中有很多关键字(EndError 很常见)。使用JsonProperty 创建别名(将“0”或“1”映射到合法的东西):

Public Class Result
    <JsonProperty("0")>
    Public Property StatusA As StatusS
    <JsonProperty("1")>
    Public Property StatusB As StatusO
    Public Property records As List(Of Record)
    Public Property success As Boolean
End Class

在他们提供数组的任何地方,您都可以使用List(Of T)

Dim jData = JsonConvert.DeserializeObject(Of Response())(jstr)

Console.WriteLine(jData(0).action)
Console.WriteLine(jData(0).result.records(0).id)
Console.WriteLine(jData(0).result.StatusA.success)
Console.WriteLine(jData(0).result.StatusA.successes(0)(0).ToString)
Console.WriteLine(jData(0).result.StatusB.successes(0).ToString)

结果:

任务系统
22920
真的
使用 S/N 成功创建任务 #22920
[
“使用 S/N #22921 成功创建任务”
]

很难发现,但[ "Task successfuly ... #22920" ] 之后缺少/额外的逗号导致Object 版本与String()() 一个或另一个可能是错字。如果没有,您可以为这两者编写一个转换器,或者在StatusO 类中添加一个方法,以使用 Q+D 方法来删​​除括号:

Friend Function GetSuccessMsg(ndx As Int32) As String
    If ndx < successes.Count Then
        Return successes(ndx).ToString().
            Replace("[", "").Replace("]", "").
            Replace("""", "").Trim()
    End If
    Return String.Empty
End Function

Console.WriteLine(jData(0).result.StatusA.GetSuccessMsg(0))

使用 S/N #22921 成功创建任务

【讨论】:

    【解决方案2】:

    这里有两个不相关的问题:

    1. 您的Result 类由一组固定属性和一组可变属性组成,这些属性具有递增的数字名称和值的标准化架构。您希望自动反序列化标准属性并捕获和反序列化自定义属性。

      这可以使用JsonExtensionData 来完成。使用此属性,您可以暂时将自定义属性反序列化为 Dictionary(of String, JToken),然后在 [OnDeserialized] 回调中转换为 Dictionary(Of String, Success)。这里Success 是一个被设计用来捕获 JSON 的类型:

      {
        "success": true,
        "successes": [ [ "Task successfuly created with S/N #22920" ] ]
      }
      

      有关文档,请参阅Deserialize ExtensionData

    2. 在上述Success 类型中,"successes" 数组包含字符串数组和单个字符串。

      如果您将successes 属性定义如下:

      Public Property successes As List(Of List(Of String))
      

      然后这可以使用How to handle both a single item and an array for the same property using JSON.net 中的SingleOrArrayConverter(Of String) 的变体来处理,通过&lt;JsonProperty(ItemConverterType := GetType(SingleOrArrayConverter(Of String)))&gt; 将其设置为item converter

    因此您的最终课程将如下所示:

    Public Class Success
        Public Property success As Boolean
        <JsonProperty(ItemConverterType := GetType(SingleOrArrayConverter(Of String)))> _   
        Public Property successes As List(Of List(Of String))
    End Class
    
    Public Class Record
        Public Property id As Integer
    End Class
    
    Public Class Result
        Public Property records As Record()
        Public Property success As Boolean
    
        <JsonIgnore> _
        Public Property successes as Dictionary(Of string, Success)
    
        <JsonExtensionData> _
        Private _additionalData as Dictionary(Of string, JToken)
    
        <System.Runtime.Serialization.OnSerializing> _
        Sub OnSerializing(ByVal context as System.Runtime.Serialization.StreamingContext)
            If successes IsNot Nothing
                _additionalData = successes.ToDictionary(Function(p) p.Key, Function(p) JToken.FromObject(p.Value))
            Else
                _additionalData = Nothing
            End If
        End Sub
    
        <System.Runtime.Serialization.OnSerialized> _
        Sub OnSerialized(ByVal context as System.Runtime.Serialization.StreamingContext)
            _additionalData = Nothing
        End Sub
    
        <System.Runtime.Serialization.OnDeserializing> _
        Sub OnDeserializing(ByVal context as System.Runtime.Serialization.StreamingContext)
            _additionalData = Nothing
        End Sub
    
        <System.Runtime.Serialization.OnDeserialized>
        Sub OnDeserialized(ByVal context as System.Runtime.Serialization.StreamingContext)
    
            If _additionalData IsNot Nothing
                successes = _additionalData.ToDictionary(Function(p) p.Key, Function(p) p.Value.ToObject(Of Success)())
            End If
            _additionalData = Nothing
    
        End Sub
    End Class
    
    Public Class Response
        Public Property type As String
        Public Property tid As Integer
        Public Property action As String
        Public Property method As String
        Public Property result As Result
    End Class
    
    Public Class SingleOrArrayConverter(Of T)
        Inherits JsonConverter
    
        Public Overrides ReadOnly Property CanWrite() As Boolean
            Get
                Return false
            End Get
        End Property
    
        Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
            Throw New NotImplementedException()
        End Sub
    
        Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
            Dim retVal As Object = New List(Of T)()
            If reader.TokenType = JsonToken.StartArray Then
                serializer.Populate(reader, retVal)
            Else
                Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
                retVal.Add(instance)
            End If
            Return retVal
        End Function
    
        Public Overrides Function CanConvert(objectType As Type) As Boolean
            Return objectType = GetType(List(Of T))
        End Function
    
    End Class
    

    原型fiddle.

    【讨论】:

    • 非常感谢,这个反序列化的数据正确完整,解决了我的问题。此外,我学到了一些我不知道的关于 JSON.Net 的功能。我将来会尝试自己解决这些问题:-)