[WCF] Verarbeitung von JSON

    • VB.NET

    Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von Lukas.

      [WCF] Verarbeitung von JSON

      Was ist JSON?
      JSON ist, wie XML, ein Datenformat. Es unterscheidet sich jedoch im Aufbau und der Leserlichkeit, weshalb es bei unkomplizierteren Einsatzgebieten oft Anklang findet. Zudem hat JSON einen geringeren Overhead. Wer mehr wissen will, kann sich ja auf Wikipedia umschauen ;)

      Wie funktioniert diese Methode?
      In diesem Tutorial wird die Funktionsweise des DataContractJsonSerializer erklärt. Grundsätzlich besteht das Verfahren daraus, dass das JSON-Objekt 1:1 in ein .NET-Objekt übertragen wird und somit gleich einsetzbar ist. Natürlich geht das auch in die andere Richtung.

      Vorbereitung
      Verweis auf System.Runtime.Serialization setzen und System.Runtime.Serialization.Json importieren.

      Lesen (Deserialisierung) von JSON
      Zunächst mal haben wir diese Ausgabe, die wir gerne in unserem Programm verarbeiten möchten:

      Quellcode

      1. {
      2. "Herausgeber": "Xema",
      3. "Nummer": "1234-5678-9012-3456",
      4. "Deckung": 2e+6,
      5. "Waehrung": "EURO",
      6. "Inhaber": {
      7. "Name": "Mustermann",
      8. "Vorname": "Max",
      9. "maennlich": true,
      10. "Hobbys": [ "Reiten", "Golfen", "Lesen" ],
      11. "Alter": 42,
      12. "Kinder": [],
      13. "Partner": null
      14. }
      15. }


      Der erste Schritt ist, dieses Modell auf .NET anzupassen. Hierfür empfehle ich json2csharp.com. Das konvertiert zwar zu C#, aber mit einem Konverter ist das kein Problem.

      Hinweis: Klassen, die hierdurch entstehen, können mit dem DataContract-, Felder mit dem DataMember-Attribut ausgestattet werden. Dies ist für das Lesen (deserialisieren) jedoch nicht notwendig. Laut MSDN scheint dies nur für die Serialisierung notwendig zu sein. Der Komplettheit zuliebe habe ich es hier nun eingefügt. Am Ende sollten wir dann dieses Modell haben:

      VB.NET-Quellcode

      1. <DataContract()>
      2. Public Class Inhaber
      3. <DataMember()>
      4. Public Property Name() As String
      5. <DataMember()>
      6. Public Property Vorname() As String
      7. <DataMember()>
      8. Public Property männlich() As Boolean
      9. <DataMember()>
      10. Public Property Hobbys() As List(Of String)
      11. <DataMember()>
      12. Public Property Alter() As Integer
      13. <DataMember()>
      14. Public Property Kinder() As List(Of Object)
      15. <DataMember()>
      16. Public Property Partner() As Object
      17. End Class
      18. <DataContract()>
      19. Public Class RootObject
      20. <DataMember()>
      21. Public Property Herausgeber() As String
      22. <DataMember()>
      23. Public Property Nummer() As String
      24. <DataMember()>
      25. Public Property Deckung() As Double
      26. <DataMember()>
      27. Public Property Waehrung() As String
      28. <DataMember()>
      29. Public Property Inhaber() As Inhaber
      30. End Class


      Falls wir vor dem Problem stehen, dass die JSON-Ausgabe unmögliche Namen hat, können wir dies mittels des DataMember-Attributes lösen. Es ermöglicht, die Eigenschaften des .NET-Objektes umzubenennen, sodass es aber immer noch als sein JSON-Äquivalent erkannt und befüllt werden kann. Nehmen wir als Beispiel das Feld "Inhaber.maennlich".

      VB.NET-Quellcode

      1. <DataMember(Name:="maennlich")>
      2. Public Property männlich() As Boolean


      Ich denke die Verwendung sollte selbsterklärend sein.

      Kommen wir zum Eigentlichen. Die Serialisierung wird über den DataContractJsonSerializer abgewickelt. Um diese Aufgabe zu erleichtern, habe ich eine Helper-Klasse geschrieben:

      VB.NET-Quellcode

      1. Public Class JSONHelper
      2. Public Shared Function FromString(Of T)(input As String) As T
      3. If String.IsNullOrWhiteSpace(input) Then
      4. Throw New ArgumentException("input")
      5. End If
      6. Dim jsonSerializer = New DataContractJsonSerializer(GetType(T))
      7. Using ms As New MemoryStream(Encoding.UTF8.GetBytes(input))
      8. Return DirectCast(jsonSerializer.ReadObject(ms), T)
      9. End Using
      10. End Function
      11. End Class


      Der Einsatz sieht dann so aus:

      VB.NET-Quellcode

      1. Dim jsonObject As RootObject = JSONHelper.FromString(Of RootObject)(jsonString)


      Nun sollte ein Objekt erzeugt worden sein, welches der JSON-Ausgabe entspricht und über dessen Eigenschaften die einzelnen Details abrufbar sind:

      VB.NET-Quellcode

      1. jsonObject.Herausgeber

      gibt "Xema" zurück.



      Schreiben (Serialisierung) von JSON
      Wie oben bereits angesprochen, ist es für die Serialisierung notwendig, Felder mit dem DataMember-, Klassen mit dem DataContract-Attribut zu versehen.

      Nehmen wir das folgende Modell als Beispiel:

      VB.NET-Quellcode

      1. <DataContract()>
      2. Class Auto
      3. Public Sub New(ByVal marke As String, ByVal name As String, ByVal nummer As Integer, ByVal nummernschild As Nummernschild)
      4. Me.Marke = marke
      5. Me.Name = name
      6. Me.Nummer = nummer
      7. Me.Nummernschild = nummernschild
      8. End Sub
      9. <DataMember()>
      10. Public Property Marke() As String
      11. <DataMember()>
      12. Public Property Name() As String
      13. <DataMember()>
      14. Public Property Nummer() As Integer
      15. <DataMember()>
      16. Public Property Nummernschild() As Nummernschild
      17. End Class
      18. <DataContract()>
      19. Class Nummernschild
      20. Public Sub New(ByVal kürzel As String, ByVal kennung As String, ByVal nummer As Integer)
      21. Me.Kürzel = kürzel
      22. Me.Kennung = kennung
      23. Me.Nummer = nummer
      24. End Sub
      25. <DataMember(Name:="Kuerzel", Order:=1)>
      26. Public Property Kürzel() As String
      27. <DataMember(Order:=2)>
      28. Public Property Kennung() As String
      29. <DataMember(Order:=3)>
      30. Public Property Nummer() As Integer
      31. End Class


      Der Aufbau sollte euch von oben bereits geläufig sein. Lediglich beim DataMember-Attribut der Nummernschild-Klasse habe ich zusätzlich die Order-Eigenschaft gesetzt. Diese setzt, wie der Name schon vermuten lässt, die spätere Reihenfolge im JSON. Das habe ich nur wegen der Lesbarkeit gemacht.

      Zum Schreiben von JSON ist eine weitere Funktion von nöten. Sie gibt das Objekt als JSON-formatierten String zurück:

      VB.NET-Quellcode

      1. Public Class JSONHelper
      2. ...
      3. Public Shared Function ToJSONString(Of T)(input As Object) As String
      4. If input Is Nothing Then
      5. Throw New ArgumentException("input")
      6. End If
      7. Using ms As New MemoryStream()
      8. Dim ser As New DataContractJsonSerializer(GetType(T))
      9. ser.WriteObject(ms, input)
      10. ms.Position = 0
      11. Using sr As New StreamReader(ms)
      12. Return sr.ReadToEnd()
      13. End Using
      14. End Using
      15. End Function
      16. End Class


      Der Aufruf sieht dann z.B. so aus:

      VB.NET-Quellcode

      1. Dim autos As New List(Of Auto)
      2. autos.Add(New Auto("Audi", "A3", 73, New Nummernschild("NIK", "EEE", 13)))
      3. autos.Add(New Auto("BMW", "X5", 121, New Nummernschild("HA", "XX", 1337)))
      4. MessageBox.Show(JSONHelper.ToJSONString(Of List(Of Auto))(autos))


      und ergibt

      Quellcode

      1. [
      2. {
      3. "Marke": "Audi",
      4. "Name": "A3",
      5. "Nummer": 73,
      6. "Nummernschild": {
      7. "Kuerzel": "NIK",
      8. "Kennung": "EEE",
      9. "Nummer": 13
      10. }
      11. },
      12. {
      13. "Marke": "BMW",
      14. "Name": "X5",
      15. "Nummer": 121,
      16. "Nummernschild": {
      17. "Kuerzel": "HA",
      18. "Kennung": "XX",
      19. "Nummer": 1337
      20. }
      21. }
      22. ]

      (ok, nicht formatiert ;))

      Weitere Methoden


      @ErfinderDesRades: hat zudem ein Demoprojekt bereitgestellt, in welchem er den DataContractJSONSerializer, den JavaScriptSerializer und den XmlSerializer gegenüberstellt. Die Projektmappe findet ihr hier.

      Angehängt ist ein Beispielprojekt mit dem Inhalt dieses Tutorials.

      Viel Spaß!
      Dateien
      • JSONExamples.zip

        (184,78 kB, 406 mal heruntergeladen, zuletzt: )
      „Was daraus gefolgert werden kann ist, dass jeder intelligentere User sein Geld lieber für Bier ausgibt, um einen schönen Rausch zu haben, und nicht dieses Ranzprodukt.“

      -Auszug aus einer Unterhaltung über das iPhone und dessen Vermarktung.

      Dieser Beitrag wurde bereits 14 mal editiert, zuletzt von „Lukas“ ()

      Hi,

      ich kenne das so, dass man an die Klassen, die man aus dem JSON deserialisieren (in diesem Fall RootObject/Inhaber) will, noch ein DataContractAttribute klebt. Ebenso kenne ich das so, dass man an jede Property ein DataMemberAttribute klebt, um dem Serializer zu sagen, dass dort Daten hinkommen. Über die Name-Eigenschaft kann man notfalls noch angeben, wie das Teil im JSON heißt; wenn nicht angegeben nimmt er den Member-/Propertynamen.

      Wundert mich deshalb, dass es anscheinend auch so funktioniert. In der Dokumentation wird es so gesagt, wie ich gerade beschrieben habe (d. h. an jede Property ein DataMember kleben).

      Übrigens kann man

      VB.NET-Quellcode

      1. Public Property Vorname() As String
      2. Get
      3. Return m_Vorname
      4. End Get
      5. Set
      6. m_Vorname = Value
      7. End Set
      8. End Property
      9. Private m_Vorname As String

      Auch verkürzt schreiben, wenn man nichts besonders im Getter/Setter machen muss:

      VB.NET-Quellcode

      1. Public Property Vorname() As String ' Auto-Implemented Property (das Backing-Field wird vom Compiler erzeugt)


      Wenn man die Attribute erstmal richtig gesetzt hat, ist das ganze nicht nur auf JSON beschränkt. Auch das Konsumieren einer XML-API ist dann problemlos mit dem DataContractSerializer (Schwesterklasse vom DataContractJsonSerializer) möglich. Man könnte sogar relativ "leicht" einen eigenen Serializer schreiben.

      nikeee
      Von meinem iPhone gesendet
      Ich bin auch erst bei diesem Beispielprojekt darauf gestoßen, dass man das Attribut weglassen kann. Das DataContract-Attribut wird nur für die Serialisierung benötigt, ich nehme an, dass es sich mit DataMember genauso verhält.
      „Was daraus gefolgert werden kann ist, dass jeder intelligentere User sein Geld lieber für Bier ausgibt, um einen schönen Rausch zu haben, und nicht dieses Ranzprodukt.“

      -Auszug aus einer Unterhaltung über das iPhone und dessen Vermarktung.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Lukas“ ()

      So scheint es zu sein. Jedenfalls ist in der Dokumentation nur die Rede von der Serialisierung, nicht von der Deserialisierung.

      DataMemberAttribute - Remarks schrieb:

      Apply the DataMemberAttribute attribute in conjunction with the DataContractAttribute to identify members of a type that are part of a data contract. One of the serializers that can serialize data contracts is the DataContractSerializer.
      The data contract model is an "opt-in" model. Applying the DataMemberAttribute to a field or property explicitly specifies that the member value will be serialized. In contrast, the BinaryFormatter serializes public and private fields of a type, and the XmlSerializer serializes only public fields and properties of a type.

      Ich würde trotzdem lieber sicher gehen, und jede Property mit einem DataMember bekleben, schließlich heißt "to identify members of a type that are part of a data contract." nicht, dass es nur für die Serialisierung gilt.
      Von meinem iPhone gesendet
      Hey, ich muss in einem aktuellen Projekt auch JSON verarbeiten.
      Nach etwas Googeln habe ich dazu diesen netten Code gefunden:

      VB.NET-Quellcode

      1. Private Function json_decode(ByVal json As String)
      2. Dim jsonSerializer As New System.Web.Script.Serialization.JavaScriptSerializer
      3. Dim dict As Dictionary(Of String, Object) = jsonSerializer.Deserialize(Of Dictionary(Of String, Object))(json)
      4. Return dict
      5. End Function


      Der Funktion muss ich nur den JSON-String übergeben und bekomme ein Objekt zurück, auf das ich z.B. via json("userdata")("username") zugreifen kann.

      Ich frage mich jetzt, inwiefern sich das von der o.g. Methode unterscheidet, und wieso man sich den Aufwand machen sollte, wenn es doch viel einfacher geht.

      Es würde mich freuen, wenn mir das jemand erklären könnte. :huh:

      Gruß,
      Kilmo
      @Kilmo: Ich finde diesen JavaScriptSerializer ühaupt nicht auf meinem System - kannst du vlt. ein Sample anhängen? Vorlage ist ja da, wären ja nur 3 Zeilen dazuzucoden.

      Meist braucht man ja auch gar nicht eine reversible Umwandlung eines Objektes in einen String, sondern halt die Serialisierung in einen Stream.
      Den Stream kann man dann auf Platte schreiben, oder auch mit anneren Anwendungen kommunizieren.
      Da eine Umwandelei nach String zwischenzuschieben wäre ja nur umständlicher Overhead.

      Also imo wäre ein besseres Sample gewesen, ein Objekt wird aus einer Datei geladen (am liebsten dann inne Anwendung modifiziert) und anschließend gespeichert.
      Ohne Übungen mit Strings.

      Bisher fand ich auch noch nichts, wie man ein Json-Serialisat so lesbar hinkriegt wie Xml.
      Also mit Zeilenvorschüben und Einrückungen - weil nur dann kann man Json als lesbarer als Xml bezeichnen.

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Es gibt bei JSON viele Wege, die zum Ziel führen. Die meisten verwenden den DataContractJsonSerializer, JavaScriptSerializer oder JSON.NET (auf Codeplex).

      Hier sind ein paar aufgelistet:
      aleembawany.com/2009/05/22/json-serializers-in-net/

      Unterschiede:
      stackoverflow.com/a/9403679/785210

      Soweit ich weiß, kann JSON.NET auch die Indentation ändern. Ich weiß es nicht mehr genau, aber der JavaScriptSerializer kann das eventuell auch.

      JSON.NET:
      james.newtonking.com/projects/json-net.aspx
      Von meinem iPhone gesendet

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „nikeee13“ ()

      nikeee13 schrieb:

      Es gibt bei JSON viele Wege, die zum Ziel führen. Die meisten verwenden den DataContractJsonSerializer, JavaScriptSerializer oder JSON.NET (auf Codeplex).
      /sign

      Der JsonSerializer ist (obwohl depreciated) mMN die flexibelste Lösung von den .Net Klassen: manche im Web verwendete Konstrukte lassen sich mit dem von Microsoft als Nachfolger propagierten DataContractJsonSerializer nicht umwandeln.

      Noch angenehmer ist die NewstonSoft.JSON Library, diese hat sich mehr oder minder auch als Standard etabliert. Features wie JsonToLinQ oder anonyme Variablen findet man in den erwähnten .NET Klassen nicht.

      ErfinderDesRades schrieb:

      @Kilmo Ich finde diesen JavaScriptSerializer ühaupt nicht auf meinem System - kannst du vlt. ein Sample anhängen? Vorlage ist ja da, wären ja nur 3 Zeilen dazuzucoden.

      Hey, die System.Web.Extensions Assembly muss eingebunden werden, dann sollte das klappen.

      Aus euren vorigen Beiträgen werde ich leider immer noch nicht so recht schlau.

      Was sind denn die Nachteile wenn ich meinen obigen Code verwende?
      Bis jetzt klappt alles prima damit.

      Ich lese damit Inhalte aus einer JSON-API aus, die ich dann als String brauche um sie im Programm zu verarbeiten.
      jetzt gefunden - thx. Dir empfehle ich Option Strict On! - deiner Funktion fehlt die Deklaration des Rückgabe-Types.

      Auch den TE täte ich bitten, Option Strict On! zu machen, und sein Sample neu anzuhängen. Gewissermaßen für Leuts wie mich, die sich damit auseinandersetzen, und dabei ohne es zu merken Strict-Off-Dirty-Code proggen.

      Jdfs. habich deinen Ansatz mal für die JsonHelpers generisch gemacht:

      VB.NET-Quellcode

      1. Public Shared Function FromJavaString(Of T)(ByVal input As String) As T
      2. Dim jsonSerializer As New System.Web.Script.Serialization.JavaScriptSerializer
      3. Return jsonSerializer.Deserialize(Of T)(input)
      4. End Function
      Dabei zu beachten, dass der Java-Serialisierer natürlich nicht das DataMemberAttribute auswertet.
      Ist in diesem Falle von "Vorteil", denn dadurch ist hier wenigstens einer der Fehler im Mapping ausgebügelt.

      Zur Frage wg. Nachteil: finde eiglich keinen. Wennman Object->String haben will, ohne Mapping, dann scheint das Dingens doch zu gehen. Für mich ist halt System.Runtime.Serialisation näherliegend als System.Web.Extensions.
      Und wenn man Serialisierung in/aus einen Stream benötigt, da wäre fürs JavaScript-Dingens ein Workaround zu schreiben, den der DataContractSerializer nicht braucht.

      Falscher Fehler

      Allerdings bekomme ich damit keine ToJavaString-Methode hin - kann mir jmd helfen?:

      VB.NET-Quellcode

      1. Public Shared Function ToJavaString(Of T)(ByVal input As Object) As String
      2. Dim jsonSerializer As New System.Web.Script.Serialization.JavaScriptSerializer
      3. Dim sb As New StringBuilder
      4. jsonSerializer.Serialize(input)
      5. Return sb.ToString
      6. End Function
      Das gibt einen Leerstring zurück


      Berichtigt - Danke!:

      VB.NET-Quellcode

      1. Public Shared Function ToJavaString(Of T)(ByVal input As Object) As String
      2. Dim jsonSerializer As New System.Web.Script.Serialization.JavaScriptSerializer
      3. Return jsonSerializer.Serialize(input)
      4. End Function

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Ich möchte ja kein Spielverderber sein, aber imho gehören (Anfänger)Fragen und Diskussionen zum Thema JavascriptSerializer nicht in dieses 'Tutorial' sondern sollte im Grundlagenforum diskutiert werden.

      Lukas hat sich die Mühe gemacht ein Tutorial zum Thema [WCF]Verarbeitung von JSON zu schreiben, und das ist nun mal per se der DataContractJsonSerializer.

      Wenn er Tips/Anregungen/Verweise aus der Diskussion in den Anfangspost aufnehmen würde, so hätten wir wieder ein nettes Tutorial auf welches wir in Zukunft verweisen könnten.

      Zum Thema:
      Oft vermisst man doch ziemlich schmerzlich die fehlenden Methoden WriteString / ReadString: diese können allerdings per Extensions leicht nachgerüstet werden, z.B:
      "Extensions"

      VB.NET-Quellcode

      1. Imports System.Runtime.CompilerServices ' for extension methods
      2. Imports System.Text
      3. ''' <summary>Extensions for the DataContractSerializer Class</summary>
      4. Module DataContractJsonSerializerExtensions
      5. ''' <summary>Writes an Object to its string representation</summary>
      6. <Extension()> _
      7. Function WriteString(ByVal dcs As System.Runtime.Serialization.Json.DataContractJsonSerializer, ByVal o As Object) As String
      8. Using ms As New IO.MemoryStream
      9. dcs.WriteObject(ms, o)
      10. Dim jsonString As String = Encoding.Default.GetString(ms.ToArray())
      11. Return jsonString
      12. End Using
      13. End Function
      14. ''' <summary>Reads an object from a json string</summary>
      15. <Extension()> _
      16. Function ReadString(ByVal dcs As System.Runtime.Serialization.Json.DataContractJsonSerializer, ByVal jsonString As String) As Object
      17. 'Using ms As New IO.MemoryStream(Encoding.Unicode.GetBytes(jsonString))
      18. Using ms As New IO.MemoryStream(Encoding.Default.GetBytes(jsonString))
      19. Dim o As Object = dcs.ReadObject(ms)
      20. Return o
      21. End Using
      22. End Function
      23. End Module


      Manchmal muss man bei unbekannten Klassen deren Aufbau erst anhand des JsonString analysiert werden: dabei hilft ein JSONFormatter , wie hier JSON Formatter & Validator
      Hey,

      @ErfinderDesRades: @Kangaroo: Ich werde mich vielleicht nachher oder morgen mal dransetzen.

      Dass Option Strict auf Off war, lag daran, dass ich eigentlich nur noch mit C# arbeite. Ich habe einen Großteil des im Sample enthaltenen Codes ursprünglich in C# geschrieben und nicht darauf geachtet, wie die VB-Optionen aussehen.
      „Was daraus gefolgert werden kann ist, dass jeder intelligentere User sein Geld lieber für Bier ausgibt, um einen schönen Rausch zu haben, und nicht dieses Ranzprodukt.“

      -Auszug aus einer Unterhaltung über das iPhone und dessen Vermarktung.
      ich hab ein klein WinForms-Anwendung (vb2010) gebastelt, wo Serialisierung via DataContractJsonSerializer, JavaScriptSerializer und XmlSerializer gegenübergestellt werden, und das unserialisierte Auto kannman im PropertyGrid angugge.
      Ans Auto habich noch Räder drangemacht, um auch im einzelnen (Auto-) Objekt eine Array-Property zu haben, die mit-serialisiert wird.


      Serialisiert wird jeweils eine List(Of Auto) mit 2 Autos drinne:

      (Beachte, dass aufgrund eines entsprechenden <DataMember>-Attributes der "Kürzel"-Property diese namensabweichend als "Kuerzel" serialisiert ist)
      Dateien
      • JsonTests01.zip

        (17,62 kB, 386 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

      @ErfinderDesRades: Ich hab einen Hinweis auf dein Projekt mal in den Startpost gepackt.
      „Was daraus gefolgert werden kann ist, dass jeder intelligentere User sein Geld lieber für Bier ausgibt, um einen schönen Rausch zu haben, und nicht dieses Ranzprodukt.“

      -Auszug aus einer Unterhaltung über das iPhone und dessen Vermarktung.