使用多个相同类型但名称不同的元素来初始化JSON

apeeds0o  于 2023-11-20  发布在  其他
关注(0)|答案(1)|浏览(73)

我试图在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中删 debugging 误和时间戳(所以只有item s,然后我可以用result = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Dictionary(Of String, ItemObj))(Jsonstring)填充对象(基于答案here)。如果我留下Errorstimestamp元素,那么我会得到一个异常:
无法将当前JSON数组(例如[1,2,3])转换为类型“PrinterAudit.RootObject”,因为该类型需要JSON对象(例如{“name”:“value”})才能正确转换。
如果所有的元素都是自己的数组或元素,那么这就很容易了,但是事实上,Errorstimestamp元素与Item s处于同一级别,这真的让我很困惑。

编辑

使用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”。“

mftmpeh8

mftmpeh81#

这是一个C#的例子,你可以在2个周期内完成,首先将常见的东西格式化,然后设置对象的数组(我曾经通过“项目”过滤-你可以找到一些像不是错误和不是时间戳之类的东西....)。
另外,你可以用字典代替列表,并添加名字作为参考键。

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; }
}

字符串
结果为:x1c 0d1x
UPDATE:你可以做反向逻辑:在类中有

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>());
    }


所以结果会更合适


相关问题