提问人:NapkinBob 提问时间:11/14/2023 最后编辑:NapkinBob 更新时间:11/18/2023 访问量:48
使用具有不同名称的相同类型的多个元素反序列化 JSON
Deserializing JSON with multiple elements of the same type with different name
问:
我正在尝试在 VB.net 4.5 中将一些 json 反序列化为自定义对象
Json 结构可以有多个具有不同名称的元素,并且名称可能不相同,因此我无法为每个可能的名称创建静态类,因为我不知道它们会是什么。所有子元素的结构都是相同的。我无法控制 json 的结构,它来自另一个应用程序。
下面是 JSON 的简化版本:
{
"Item1": {
"config": {
"PortInfo" : {
"baud": 19200,
"dataLength": 8,
"flowControl": 0,
"parity": 0
},
"Custom": {
"116": 1,
"117": 2,
"129": 85,
"123": 1,
"124": 0,
"125": 2
}
}
},
"Item3": {
"config": {
"PortInfo" : {
"baud": 19200,
"dataLength": 8,
"flowControl": 0,
"parity": 0
},
"Custom": {
"116": 1,
"117": 2,
"129": 85,
"123": 1,
"124": 0,
"125": 2
}
}
},
"Item4": {
"config": {
"PortInfo" : {
"baud": 19200,
"dataLength": 8,
"flowControl": 0,
"parity": 0
},
"Custom": {
"116": 1,
"117": 2,
"129": 85,
"123": 1,
"124": 0,
"125": 2
}
}
},
"Errors": [
[
"com2",
"port busy"
],
[
"com4",
"port busy"
]
],
"timestamp": "2023-11-13 15:31:29"
}
我创建了以下类来保存数据:
Public Class Rootobject
Public Property Item As ItemObj
Public Property Errors As String()
Public Property timestamp As String
End Class
Public Class ItemObj
Public Property config As Config
End Class
Public Class Config
Public Property PortInfo As Portinfo
Public Property Custom As Dictionary(Of String, Integer)
End Class
Public Class Portinfo
Public Property baud As Integer
Public Property dataLength As Integer
Public Property flowControl As Integer
Public Property parity As Integer
End Class
我不知道如何将此 JSON 反序列化到我的对象中,从而导致
- 元素的字典(或某种列表)
Item
- 单个阵列
Errors
- 单个元素
timestamp
我得到的最接近的是,如果我从 JSON 中删除错误和时间戳(所以它只是 s,然后我可以用(基于这里的答案)填充对象。如果我留下 and 元素,那么我会得到一个异常,说:item
result = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Dictionary(Of String, ItemObj))(Jsonstring)
Errors
timestamp
无法将当前 JSON 数组(例如 [1,2,3])反序列化为类型“PrinterAudit.Rootobject”,因为该类型需要 JSON 对象(例如 {“name”:“value”})才能正确反序列化。
如果所有项目都是它们自己的数组或元素,那么这很容易,但事实上,元素与 s 处于同一级别,这真的让我失望了。Errors
timestamp
Item
编辑
使用 dbc 的解决方案,我尝试转换为 VB,从而产生以下转换器:
Imports System.Runtime.CompilerServices
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Imports Newtonsoft.Json.Serialization
<AttributeUsage((AttributeTargets.Field Or AttributeTargets.Property), AllowMultiple:=False)>
Public Class JsonTypedExtensionDataAttribute
Inherits Attribute
End Class
Public Class TypedExtensionDataConverter(Of TObject)
Inherits JsonConverter
Public Overrides Function CanConvert(ByVal objectType As Type) As Boolean
Return GetType(TObject).IsAssignableFrom(objectType)
End Function
Private Function GetExtensionJsonProperty(ByVal contract As JsonObjectContract) As JsonProperty
Try
Return contract.Properties.Where(Function(p) p.AttributeProvider.GetAttributes(GetType(JsonTypedExtensionDataAttribute), False).Any()).[Single]()
Catch ex As InvalidOperationException
Throw New JsonSerializationException(String.Format("Exactly one property with JsonTypedExtensionDataAttribute is required for type {0}", contract.UnderlyingType), ex)
End Try
End Function
Public Overrides Function ReadJson(ByVal reader As JsonReader, ByVal objectType As Type, ByVal existingValue As Object, ByVal serializer As JsonSerializer) As Object
If (reader.TokenType = JsonToken.Null) Then
Return Nothing
End If
Dim jObj = JObject.Load(reader)
Dim contract = CType(serializer.ContractResolver.ResolveContract(objectType), JsonObjectContract)
Dim extensionJsonProperty = Me.GetExtensionJsonProperty(contract)
Dim extensionJProperty = CType(Nothing, JProperty)
Dim i As Integer = (jObj.Count - 1)
Do While (i >= 0)
Dim prop = CType(jObj.AsList(i), JProperty)
If contract.Properties.GetClosestMatchProperty(prop.Name) Is Nothing Then
If (extensionJProperty Is Nothing) Then
extensionJProperty = New JProperty(extensionJsonProperty.PropertyName, New JObject)
jObj.Add(extensionJProperty)
End If
CType(extensionJProperty.Value, JObject).Add(prop.RemoveFromLowestPossibleParent)
End If
i = (i - 1)
Loop
Dim value = If(existingValue, contract.DefaultCreator())
Using subReader = jObj.CreateReader
serializer.Populate(subReader, value)
End Using
Return value
End Function
Public Overrides Sub WriteJson(ByVal writer As JsonWriter, ByVal value As Object, ByVal serializer As JsonSerializer)
Dim contract = CType(serializer.ContractResolver.ResolveContract(value.GetType), JsonObjectContract)
Dim extensionJsonProperty = Me.GetExtensionJsonProperty(contract)
Dim jObj As JObject
Using New PushValue(Of Boolean)(True, Function() Disabled, Function(canWrite) CSharpImpl.__Assign(Disabled, canWrite))
jObj = JObject.FromObject(value, serializer)
End Using
Dim extensionValue = CType(jObj(extensionJsonProperty.PropertyName), JObject).RemoveFromLowestPossibleParent
If (Not (extensionValue) Is Nothing) Then
Dim i As Integer = (extensionValue.Count - 1)
Do While (i >= 0)
Dim prop = CType(extensionValue.AsList(i), JProperty)
jObj.Add(prop.RemoveFromLowestPossibleParent)
i = (i - 1)
Loop
End If
jObj.WriteTo(writer)
End Sub
Private Class CSharpImpl
<Obsolete("Please refactor calling code to use normal Visual Basic assignment")>
Shared Function __Assign(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
End Class
<ThreadStatic()>
Private Shared _disabled As Boolean
' Disables the converter in a thread-safe manner.
Private Property Disabled As Boolean
Get
Return _disabled
End Get
Set
_disabled = Value
End Set
End Property
Public Overrides ReadOnly Property CanWrite As Boolean
Get
Return Not Me.disabled
End Get
End Property
Public Overrides ReadOnly Property CanRead As Boolean
Get
Return Not Me.disabled
End Get
End Property
End Class
Public Structure PushValue(Of T)
Implements IDisposable
Private setValue As Action(Of T)
Private oldValue As T
Public Sub New(ByVal value As T, ByVal getValue As Func(Of T), ByVal setValue As Action(Of T))
If getValue Is Nothing OrElse setValue Is Nothing Then Throw New ArgumentNullException()
Me.setValue = setValue
Me.oldValue = getValue()
setValue(value)
End Sub
#Region "IDisposable Members"
Public Sub Dispose() Implements IDisposable.Dispose
If (Not (Me.setValue) Is Nothing) Then
setValue(Me.oldValue)
End If
End Sub
#End Region
End Structure
Public Module JsonExtensions
<Extension()>
Public Function RemoveFromLowestPossibleParent(Of TJToken As JToken)(ByVal node As TJToken) As TJToken
If node Is Nothing Then Return Nothing
Dim contained = node.AncestorsAndSelf().Where(Function(t) TypeOf t.Parent Is JContainer AndAlso t.Parent.Type <> JTokenType.[Property]).FirstOrDefault()
If contained IsNot Nothing Then contained.Remove()
' Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
If TypeOf node.Parent Is JProperty Then CType(node.Parent, JProperty).Value = Nothing
Return node
End Function
<Extension()>
Public Function AsList(ByVal container As IList(Of JToken)) As IList(Of JToken)
Return container
End Function
End Module
在我的一生中,我无法弄清楚如何重构“过时”的函数,所以我把它留在那里。我不确定这是否有影响。CSharpImpl__Assign(Of T)
下面是现在更新为使用转换器的类:
Imports Newtonsoft.Json
<JsonConverter(GetType(TypedExtensionDataConverter(Of Rootobject)))>
Public Class Rootobject
Public Sub New()
Item = New Dictionary(Of String, ItemObj)
End Sub
<JsonTypedExtensionData>
Public Property Item As Dictionary(Of String, ItemObj)
<JsonProperty("Errors")>
Public Property Errors As List(Of List(Of String))
<JsonProperty("timestamp")>
Public Property timestamp As String
End Class
Public Class ItemObj
Public Property config As Config
End Class
Public Class Config
Public Property PortInfo As Portinfo
Public Property Custom As Dictionary(Of String, Integer)
End Class
Public Class Portinfo
Public Property baud As Integer
Public Property dataLength As Integer
Public Property flowControl As Integer
Public Property parity As Integer
End Class
在我的主要方法中,我有以下内容:
Dim Jsonstr As String = "{""Item1"":{""config"":{""PortInfo"":{""baud"":19200,""dataLength"":8,""flowControl"":0,""parity"":0},""Custom"":{""116"":1,""117"":2,""123"":1,""124"":0,""125"":2,""129"":85}}},""Item3"":{""config"":{""PortInfo"":{""baud"":19200,""dataLength"":8,""flowControl"":0,""parity"":0},""Custom"":{""116"":1,""117"":2,""123"":1,""124"":0,""125"":2,""129"":85}}},""Item4"":{""config"":{""PortInfo"":{""baud"":19200,""dataLength"":8,""flowControl"":0,""parity"":0},""Custom"":{""116"":1,""117"":2,""123"":1,""124"":0,""125"":2,""129"":85}}},""Errors"":[[""com2"",""port busy""],[""com4"",""port busy""]],""timestamp"":""2023-11-13 15:31:29""}"
Dim result = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Rootobject)(Jsonstr)
在运行时,我收到错误:
System.InvalidCastException:“无法强制转换类型的对象 “System.Func”1[System.Object]“键入”PrinterAudit.Rootobject”。
答:
这是一个 C# 示例,您可以在 2 个周期内完成,首先反序列化常见内容,其次 - 设置对象数组(我曾经按“Item”过滤 - 您可以找到诸如不是错误而不是时间戳之类的东西......
此外,您可以拥有字典而不是列表,并添加名称作为参考键......
void Main()
{
string json = "{\"Item1\":{\"config\":{\"PortInfo\":{\"baud\":19200,\"dataLength\":8,\"flowControl\":0,\"parity\":0},\"Custom\":{\"116\":1,\"117\":2,\"129\":85,\"123\":1,\"124\":0,\"125\":2}}},\"Item3\":{\"config\":{\"PortInfo\":{\"baud\":19200,\"dataLength\":8,\"flowControl\":0,\"parity\":0},\"Custom\":{\"116\":1,\"117\":2,\"129\":85,\"123\":1,\"124\":0,\"125\":2}}},\"Item4\":{\"config\":{\"PortInfo\":{\"baud\":19200,\"dataLength\":8,\"flowControl\":0,\"parity\":0},\"Custom\":{\"116\":1,\"117\":2,\"129\":85,\"123\":1,\"124\":0,\"125\":2}}},\"Errors\":[[\"com2\",\"port busy\"],[\"com4\",\"port busy\"]],\"timestamp\":\"2023-11-13 15:31:29\"}";
Rootobject result = JsonConvert.DeserializeObject<Rootobject>(json);
var parsed = JObject.Parse(json);
foreach (var el in parsed)
{
if (el.Key.Contains("Item"))
result.Items.Add(el.Value.ToObject<ItemObj>());
}
result.Dump();
}
public class Rootobject
{
public Rootobject() {
Items = new List<ItemObj>();
}
public List<ItemObj> Items {get;set;}
[JsonProperty("Errors")]
public List<List<string>> Errors { get; set; }
[JsonProperty("timestamp")]
public string timestamp { get; set; }
}
public class ItemObj
{
public Config config { get; set; }
}
public class Config
{
public Portinfo PortInfo { get; set; }
public Dictionary<string, int> Custom { get; set; }
}
public class Portinfo
{
public int baud { get; set; }
public int dataLength { get; set; }
public int flowControl { get; set; }
public int parity { get; set; }
}
更新:你可以做反向逻辑: 在课堂上
public Rootobject() {
Items = new Dictionary<string,ItemObj>();
}
public Dictionary<string, ItemObj> Items {get;set;}
在代码中:
foreach (var el in parsed)
{
if (!new string[] {"timestamp", "errors"}.Contains(el.Key.ToLower()))
result.Items.Add(el.Key, el.Value.ToObject<ItemObj>());
}
所以结果会更合适
评论
"Errors"
"timestamp"
ReadJson
'Unable to cast object of type 'System.Func``1[System.Object]' to type 'PrinterAudit.Rootobject'.'
SomeFunction()()