【问题标题】:Deserialize JSON property when it can be a single string or a jarray with Newtonsoft当它可以是单个字符串或使用 Newtonsoft 的 jarray 时,反序列化 JSON 属性
【发布时间】:2020-05-20 04:57:50
【问题描述】:

我正在从 Web 服务器检索 JSON 响应,并为所有属性创建了强类型类,没有任何问题,除了一个可以是单个字符串值或嵌套数组的属性。

JSON 示例:

"meta_data": [
{
  "id": 12772,
  "key": "_shipping_phone",
  "value": ""
},
{
  "id": 12786,
  "key": "status",
  "value": "completed"
},
{
  "id": 12788,
  "key": "Payment type",
  "value": "instant"
},
{
  "id": 12796,
  "key": "_transaction_fee",
  "value": "0.39"
},
{
  "id": 12806,
  "key": "connect_destination_normalized",
  "value": "1"
},
{
  "id": 12807,
  "key": "wc_connect_labels",
  "value": [
    {
      "label_id": 1633947,
      "tracking": "9400***************",
      "refundable_amount": 3.930000000000000159872115546022541821002960205078125,
      "created": 1589499950667,
      "carrier_id": "usps",
      "service_name": "USPS - First Class Mail",
      "status": "PURCHASED",
      "package_name": "Bubble Mailer",
      "product_names": [
        "Peace Love And Wine Sub Tshirt"
      ],
      "receipt_item_id": 60476008,
      "created_date": 1589499955000,
      "main_receipt_id": 46201718,
      "rate": 3.930000000000000159872115546022541821002960205078125,
      "currency": "USD",
      "expiry_date": 1605051955000,
      "label_cached": 1589499962000
    },
    {
      "label_id": 1633942,
      "tracking": null,
      "refundable_amount": 0,
      "created": 1589499912741,
      "carrier_id": null,
      "service_name": "USPS - First Class Mail",
      "status": "PURCHASE_ERROR",
      "package_name": "Bubble Mailer",
      "product_names": [
        "Peace Love And Wine Sub Tshirt"
      ],
      "receipt_item_id": -1,
      "created_date": 1589499912000,
      "error": "The transaction was declined."
    },
    {
      "label_id": 1633913,
      "tracking": null,
      "refundable_amount": 0,
      "created": 1589499712367,
      "carrier_id": null,
      "service_name": "USPS - First Class Mail",
      "status": "PURCHASE_ERROR",
      "package_name": "Bubble Mailer",
      "product_names": [
        "Peace Love And Wine Sub Tshirt"
      ],
      "receipt_item_id": -1,
      "created_date": 1589499712000,
      "error": "The transaction was declined."
    }
  ]
}

如您所见,value 键是一个普通的字符串值(或空白字符串)。但是,最终条目显示的内容可能更多。

这是我的强类型类:

Public Class Meta_Data
    Public Property id As Integer
    Public Property key As String
    '<JsonConverter(GetType(SingleOrArrayConverter(Of Values())))>
    Public Property value As Values()
End Class

Public Class Values
    Public Property label_id As Integer
    Public Property tracking As String
    Public Property refundable_amount As String
    Public Property created As String
    Public Property carrier_id As String
    Public Property service_name As String
    Public Property status As String
    Public Property package_name As String
    Public Property product_names As String()
    Public Property receipt_item_id As String
    Public Property created_date As String
    Public Property [error] As String
    Public Property main_receipt_id As String
    Public Property rate As String
    Public Property currency As String
    Public Property expiry_date As String
    Public Property label_cached As String
End Class

我已经在互联网上搜索了解决方案,但无法提出解决方案。我已经尝试了我找到的不同转换器示例,但没有一个有效,因为它们仍然尝试通过 Values 类传递单个字符串。

这是我的反序列化调用:

Dim info As Order = JsonConvert.DeserializeObject(Of Order)(responseFromServer)

我还有多个其他类,我不打算占用空间来展示,但根类称为Order。 基本上,有没有办法在处理反序列化时检查value,看看它是否是一个字符串,并防止它通过Values类处理?

【问题讨论】:

    标签: json vb.net json.net


    【解决方案1】:

    由于您希望以不同于数组的方式处理单个字符串值,因此您需要更改模型以使其具有多态性。也就是说,创建一个包含所有meta_data 项目共有的idkey 属性的基类,然后创建子类来表示字符串和数组变体。所以,是这样的:

    Public Class Order
        Public Property meta_data As List(Of BaseMetaData)
    End Class
    
    <JsonConverter(GetType(MetaDataConverter))>
    Public Class BaseMetaData
        Public Property id As Integer
        Public Property key As String
    End Class
    
    Public Class StringMetaData
        Inherits BaseMetaData
        Public Property value As String
    End Class
    
    Public Class ComplexMetaData
        Inherits BaseMetaData
        Public Property value As List(Of Values)
    End Class
    
    Public Class Values
        Public Property label_id As Integer
        Public Property tracking As String
        Public Property refundable_amount As String
        Public Property created As String
        Public Property carrier_id As String
        Public Property service_name As String
        Public Property status As String
        Public Property package_name As String
        Public Property product_names As String()
        Public Property receipt_item_id As String
        Public Property created_date As String
        Public Property [error] As String
        Public Property main_receipt_id As String
        Public Property rate As String
        Public Property currency As String
        Public Property expiry_date As String
        Public Property label_cached As String
    End Class
    

    然后你可以为基类创建一个自定义的JsonConverter,它会检测值类型并创建正确的子类:

    Public Class MetaDataConverter
        Inherits JsonConverter
    
        Public Overrides Function CanConvert(objectType As Type) As Boolean
            Return GetType(BaseMetaData).IsAssignableFrom(objectType)
        End Function
    
        Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
            Dim jo As JObject = JObject.Load(reader)
            Dim val As JToken = jo("value")
            Dim meta As BaseMetaData
            If val.Type = JTokenType.Array Then
                meta = New ComplexMetaData()
            Else
                meta = New StringMetaData()
            End If
            serializer.Populate(jo.CreateReader(), meta)
            Return meta
        End Function
    
        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
    End Class
    

    最后一点是用JsonConverter 属性标记BaseMetaData,我在上面的模型声明中已经这样做了。然后,您可以像往常一样反序列化,一切都应该“正常工作”。

    这是一个工作演示:https://dotnetfiddle.net/3uYXKu

    【讨论】:

      【解决方案2】:

      选项 1

      使用定义了两种不同类型的结构:StringList(Of Class)(其中Class 是当value 是复杂对象时生成的类对象)。

      主类名为OrderRoot,它包含:

      • Json 反序列化器生成相应 .Net 类型所需的所有其他类的定义
      • 两个自定义转换器:
        • OrderTypeConverter 用于将 value property 反序列化/序列化为 String 类型或 List(Of class),并在需要时将其序列化回原始值
        • ValueObjectDateConverter,用于在序列化时将 .Net DateTime 格式的 Unix 日期转换回 Unix 长日期格式。
      • 自定义Deserialize()Serialize() 方法,以拥有一个紧凑的类对象,该对象调用相应的Newtonsoft.Json 方法,而无需进一步干预外部代码。

      Value 属性现在是 double faced 属性:当其 StringValue 字段为空或为空时,您知道它的 @ 987654338@ 字段包含List(Of Value) 对象。


      反序列化服务器响应:

      Dim orderInfo = New OrderRoot.OrderInfo().Deserialize(responseFromServer)
      

      将其序列化回原始 JSON 格式:

      Dim json As String = orderInfo.Serialize()
      

      OrderRoot 类对象定义:

      Imports System.Globalization
      Imports Newtonsoft.Json
      Imports Newtonsoft.Json.Converters
      
      Public Class OrderRoot
          Public Class OrderInfo
              <JsonProperty("meta_data")>
              Public Property MetaData As List(Of MetaData)
              Public Function Deserialize(json As String) As OrderInfo
                  Return JsonConvert.DeserializeObject(Of OrderInfo)(json, OrderTypeConverter.Settings)
              End Function
      
              Public Function Serialize() As String
                  Return JsonConvert.SerializeObject(Me, Formatting.Indented, OrderTypeConverter.Settings)
              End Function
          End Class
      
          Public Class MetaData
              <JsonProperty("id")>
              Public Property Id As Long
              <JsonProperty("key")>
              Public Property Key As String
              <JsonProperty("value")>
              Public Property Value As ValueMetaObject
          End Class
      
          Partial Public Class ValueObject
              <JsonProperty("label_id")>
              Public Property LabelId As Long
              <JsonProperty("tracking")>
              Public Property Tracking As String
              <JsonProperty("refundable_amount")>
              Public Property RefundableAmount As Double
              <JsonConverter(GetType(ValueObjectDateConverter))>
              <JsonProperty("created")>
              Public Property Created As DateTimeOffset?
              <JsonProperty("carrier_id")>
              Public Property CarrierId As String
              <JsonProperty("service_name")>
              Public Property ServiceName As String
              <JsonProperty("status")>
              Public Property Status() As String
              <JsonProperty("package_name")>
              Public Property PackageName As String
              <JsonProperty("product_names")>
              Public Property ProductNames As List(Of String)
              <JsonProperty("receipt_item_id")>
              Public Property ReceiptItemId As Long
              <JsonConverter(GetType(ValueObjectDateConverter))>
              <JsonProperty("created_date")>
              Public Property CreatedDate As DateTimeOffset?
              <JsonProperty("main_receipt_id")>
              Public Property MainReceiptId As Long
              <JsonProperty("rate")>
              Public Property Rate As Double
              <JsonProperty("currency")>
              Public Property Currency As String
              <JsonConverter(GetType(ValueObjectDateConverter))>
              <JsonProperty("expiry_date")>
              Public Property ExpiryDate As DateTimeOffset?
              <JsonConverter(GetType(ValueObjectDateConverter))>
              <JsonProperty("label_cached")>
              Public Property LabelCached As DateTimeOffset?
              <JsonProperty("error")>
              Public Property ErrorDescription As String
          End Class
      
          Public Structure ValueMetaObject
              Public StringValue As String
              Public ArrayValue As List(Of ValueObject)
      
              Public Shared Widening Operator CType(ByVal s As String) As ValueMetaObject
                  Return New ValueMetaObject() With {.StringValue = s}
              End Operator
      
              Public Shared Widening Operator CType(ByVal lstValue As List(Of ValueObject)) As ValueMetaObject
                  Return New ValueMetaObject With {.ArrayValue = lstValue}
              End Operator
          End Structure
      
          Friend Class OrderTypeConverter
              Public Shared ReadOnly Settings As New JsonSerializerSettings() With {
                  .DateParseHandling = DateParseHandling.DateTimeOffset,
                  .MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
                  .NullValueHandling = NullValueHandling.Ignore,
                  .Converters = {
                      New ValueObjectConverter(),
                      New IsoDateTimeConverter() With {
                          .DateTimeStyles = DateTimeStyles.None
                      }
                  }
              }
          End Class
      
          Friend Class ValueObjectConverter
              Inherits JsonConverter
      
              Public Overrides Function CanConvert(t As Type) As Boolean
                  Return t = GetType(ValueMetaObject) OrElse t = GetType(ValueMetaObject?)
              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 sValue = serializer.Deserialize(Of String)(reader)
                          Return New ValueMetaObject() With {
                              .StringValue = sValue
                          }
                      Case JsonToken.StartArray
                          Dim arValue = serializer.Deserialize(Of List(Of ValueObject))(reader)
                          Return New ValueMetaObject() With {
                              .ArrayValue = arValue
                          }
                  End Select
                  Throw New Exception("Read failed")
              End Function
      
              Public Overrides Sub WriteJson(writer As JsonWriter, utValue As Object, serializer As JsonSerializer)
                  Dim value = DirectCast(utValue, ValueMetaObject)
                  If value.StringValue IsNot Nothing Then
                      serializer.Serialize(writer, value.StringValue)
                      Return
                  End If
                  If value.ArrayValue IsNot Nothing Then
                      serializer.Serialize(writer, value.ArrayValue)
                      Return
                  End If
                  Throw New Exception("Write failed")
              End Sub
          End Class
      
          Friend Class ValueObjectDateConverter
              Inherits UnixDateTimeConverter
      
              Public Overrides Function CanConvert(t As Type) As Boolean
                  Return t = GetType(Long) OrElse t = GetType(Long?)
              End Function
      
              Public Overrides Function ReadJson(reader As JsonReader, t As Type, existingValue As Object, serializer As JsonSerializer) As Object
                  Dim uxDT As Long? = serializer.Deserialize(Of Long?)(reader)
                  Return DateTimeOffset.FromUnixTimeMilliseconds(uxDT.Value)
              End Function
              Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
                  Dim dtmo = DirectCast(value, DateTimeOffset)
                  If dtmo <> DateTimeOffset.MinValue Then
                      serializer.Serialize(writer, CType(DirectCast(value, DateTimeOffset).ToUnixTimeMilliseconds(), ULong))
                  Else
                      MyBase.WriteJson(writer, Nothing, serializer)
                  End If
              End Sub
          End Class
      End Class
      

      选项 2

      只有MetaData 类中的Value Property 被更改,将其定义为Object 类型并使用JsonConverter 属性进行修饰。
      ValueObjectConverter 自定义转换器略有不同,看一下。

      Public Class MetaData
          <JsonProperty("id")>
          Public Property Id As Long
      
          <JsonProperty("key")>
          Public Property Key As String
      
          <JsonConverter(GetType(ValueObjectConverter))>
          <JsonProperty("value")>
          Public Property Value As Object
      End Class
      

      改编的自定义转换器,如果这种格式更可取,可以替换以前的转换器:

      Friend Class OrderTypeConverter
          Public Shared ReadOnly Settings As New JsonSerializerSettings() With {
              .DateParseHandling = DateParseHandling.DateTimeOffset,
              .MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
              .NullValueHandling = NullValueHandling.Ignore,
              .Converters = {
                  New IsoDateTimeConverter() With {
                      .DateTimeStyles = DateTimeStyles.None
                  }
              }
          }
      End Class
      
      Friend Class ValueObjectConverter
          Inherits JsonConverter
      
          Public Overrides Function CanConvert(t As Type) As Boolean
              Return t = GetType(String) OrElse t = GetType(Array)
          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 sValue = serializer.Deserialize(Of String)(reader)
                      Return sValue
                  Case JsonToken.StartArray
                      Dim arValue = serializer.Deserialize(Of List(Of ValueObject))(reader)
                      Return arValue
              End Select
              Throw New Exception("Read failed")
          End Function
      
          Public Overrides Sub WriteJson(writer As JsonWriter, utValue As Object, serializer As JsonSerializer)
              If utValue IsNot Nothing AndAlso TypeOf utValue Is String Then
                  serializer.Serialize(writer, utValue.ToString())
                  Return
              Else
                  serializer.Serialize(writer, DirectCast(utValue, List(Of ValueObject)))
                  Return
              End If
              Throw New Exception("Write failed")
          End Sub
      End Class
      

      Option 3

      Brian Rogers发布,所以我不会打扰:)

      【讨论】:

        猜你喜欢
        • 2022-11-27
        • 1970-01-01
        • 2022-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多