Wie JSON dekodieren?

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
Kukulkan
Beiträge: 1066
Registriert: 09.09.2004 07:07
Wohnort: Süddeutschland
Kontaktdaten:

Wie JSON dekodieren?

Beitrag von Kukulkan »

Hallo,

Ich stehe vor der Aufgabe, JSON Daten zu dekodieren. Die Daten sehen in etwa so aus (Beispieldaten):

Code: Alles auswählen

{
    "Date": "2009-10-09 09:57:11 CEST",
    "Provider1": {
        "ProviderName": "Testprovider",
        "Domain": "providerdomain",
        "PremiumCount": 31,
        "StandardCount": 199,
        "SpecialCount": 2
    },
    "Provider2": {
        "ProviderName": "Demoprovider",
        "Domain": "demodomain",
        "PremiumCount": 0,
        "StandardCount": 0,
        "SpecialCount": 0
    },
    "CurrentUserCount": 234,
    "CurrentUserCountValue": 402,
    "ActiveUsersLastMonth": 1,
    "TransactionsLastMonth": 0,
    "MaxTransactionsDefined": 200
}
Ich sollte das jetzt mit PureBasic parsen um dann später einen komfortablen Zugriff auf die Daten zu erhalten. In PHP würde ich das so machen:

Code: Alles auswählen

$Daten = json_decode($String, true);
echo $Daten["Date"];
echo $Daten["CurrentUserCount"];
echo $Daten["Provider1"]["Domain"];
Wie mache ich das in PureBasic?

Wenn es nicht geht, würde ich einen Parser schreiben. Aber wie stelle ich die Ergebnisse zur Verfügung, denn solche Arrays (mit Array im Array) sind in PB ja gar nicht möglich. Eine Idee wie man den Zugriff auf die Daten möglichst einfach realisiert ohne zig Befehle zum durchlaufen (NextNode, ParentNode, NodeCount etc.) zu benötigen?

Volker
Benutzeravatar
Kiffi
Beiträge: 10711
Registriert: 08.09.2004 08:21
Wohnort: Amphibios 9

Re: Wie JSON dekodieren?

Beitrag von Kiffi »

Hallo Volker,

klopf mal bei milan1612 an. Der hat z. Zt. einen JSON-Parser in der Mache.

Grüße ... Kiffi
a²+b²=mc²
Benutzeravatar
milan1612
Beiträge: 810
Registriert: 15.04.2007 17:58

Re: Wie JSON dekodieren?

Beitrag von milan1612 »

Kiffi hat geschrieben:Hallo Volker,

klopf mal bei milan1612 an. Der hat z. Zt. einen JSON-Parser in der Mache.

Grüße ... Kiffi
Bin schon da :wink:

@Volker
Ich könnte dir für deinen speziellen Fall einen Parser schreiben (also den Zugriffscode, der Parser ist schon fertig).
Wenn du mir genau sagts was du brauchst, werd ich dir was dazu schicken. Leider ist ein einfacher Zugriff
noch nicht möglich (stehe vor genau diesem Problem), aber für einen speziellen Fall sollte es einfach sein.

Grüße, Milan...
Bin nur noch sehr selten hier, bitte nur noch per PN kontaktieren
Benutzeravatar
Kukulkan
Beiträge: 1066
Registriert: 09.09.2004 07:07
Wohnort: Süddeutschland
Kontaktdaten:

Re: Wie JSON dekodieren?

Beitrag von Kukulkan »

Hallo Ihr beiden,

Vielen Dank, aber ich habe ja auch einen speziellen Fall. Ich weiss genau auf welche Felder ich zugreifen möchte. Weiterhin ist sicher, dass das PIPE Zeichen (|) nicht vorkommt. Deshalb konnte ich folgenden Code schreiben. Das ist kein vollständiger JSON Parser, und ich hab den nur eben in zwei Stunden hingesudelt. In meinem Fall klappt es aber ausreichend gut:

Code: Alles auswählen

; JSON decoder
; (w) & (c) 2009 by Volker Schmid
; PureBasic 4.2
;
; Utilizes Mapping-Code by NicTheQuick (July 2003)

; ##########################
; ###     MAPPING CODE   ###
; ##########################

Structure jsonMap 
  Index.s 
  Value.s 
EndStructure

Global NewList jsonMap.jsonMap() 

Procedure jsonMapPut(Index.s, Value.s) 
  Protected *z1.Byte, *z2.Byte, OK.l 
  
  ResetList(jsonMap()) 
  While NextElement(jsonMap()) 
    *z1 = @Index 
    *z2 = @jsonMap()\Index 
    While *z1\b = *z2\b And *z1\b And *z2\b 
      *z1 + 1 
      *z2 + 1 
    Wend 
    If *z1\b = *z2\b 
      jsonMap()\Value = Value 
      ProcedureReturn @jsonMap() 
    EndIf 
  Wend 
  If AddElement(jsonMap()) 
    jsonMap()\Index = Index 
    jsonMap()\Value = Value 
    ProcedureReturn @jsonMap 
  EndIf 
  ProcedureReturn #False 
EndProcedure 

Procedure.s jsonMapGet(Index.s) 
  Protected *z1.Byte, *z2.Byte, OK.l 
  
  ResetList(jsonMap()) 
  While NextElement(jsonMap()) 
    *z1 = @Index 
    *z2 = @jsonMap()\Index 
    While *z1\b = *z2\b And *z1\b And *z2\b 
      *z1 + 1 
      *z2 + 1 
    Wend 
    If *z1\b = *z2\b 
      ProcedureReturn jsonMap()\Value 
    EndIf 
  Wend 
  ProcedureReturn "" 
EndProcedure 

; ##########################
; ###      JSON DECODE   ###
; ##########################

Procedure.l jsonHex2Dec(Input.s)
  ; "FF" = 256  "65A" = 1626
  Protected i.l, d.l
  
  For i.l = 1 To Len(Input.s) 
    d.l = (d.l << 4) + Asc(UCase(Mid(Input.s, i.l, 1))) - 48 - 7 * (Asc(UCase(Mid(Input.s, i.l, 1))) >> 6) 
  Next 
  ProcedureReturn d.l
EndProcedure

Procedure.b jsonDecode(JSONString.s)
  
  ClearList(jsonMap()) ; init list
  JSONString.s = Trim(JSONString.s)
  If Left(JSONString.s, 1) <> "{": ProcedureReturn #False: EndIf
  
  Parent.s = ""
  For x.l = 2 To Len(JSONString.s)
    Char.s = Mid(JSONString.s, x.l, 1)
    Select Char.s
      Case Chr(34)
        If InString.b = #False: InString.b = #True: Else: InString.b = #False: EndIf
      
      Case ","
        If InString.b = #False And Word.s <> ""
          If InArray.b = #True: ArrayCount.l = ArrayCount.l + 1: EndIf
          LastValue.s = Word.s
          Word.s = ""
          If InArray.b = #False
            ;Debug "-> " + Parent.s + LastParameter.s + ": " + LastValue.s
            jsonMapPut(Parent.s + LastParameter.s, LastValue.s)
          Else
            ;Debug "-> " + Parent.s + LastParameter.s + "|" + Str(ArrayCount) + ": " + LastValue.s
            jsonMapPut(Parent.s + LastParameter.s + "|" + Str(ArrayCount.l), LastValue.s)
          EndIf
        EndIf
        
      Case ":"
        If InString.b = #False
          LastParameter.s = Word.s
          LastValue.s = ""
          Word.s = ""
        Else
          Word.s = Word.s + Char.s
        EndIf
      
      Case "{"
        If InString.b = #False
          Parent.s = Parent.s + LastParameter.s + "|"
          Word.s = ""
        Else
          Word.s = Word.s + Char.s
        EndIf
      
      Case "}"
        If InString.b = #False
          LastValue.s = Word.s
          Word.s = ""
          If InArray.b = #False
            If LastValue.s <> ""
              ; Debug "ia -> " + Parent.s + LastParameter.s + ": " + LastValue.s
              jsonMapPut(Parent.s + LastParameter.s, LastValue.s)
            EndIf
          Else
            ; Debug "na -> " + Parent.s + LastParameter.s + "|" + Str(ArrayCount.l) + ": " + LastValue.s
            jsonMapPut(Parent.s + LastParameter.s + "|" + Str(ArrayCount.l), LastValue.s)
          EndIf 
          NewParent.s = ""
          For y.l = 1 To CountString(Parent.s , "|")-1
            NewParent.s = StringField(Parent.s, y.l, "|") + "|"
          Next
          Parent.s = NewParent.s
        Else
          Word.s = Word.s + Char.s
        EndIf
        
      Case "["
        If InString.b = #False
          ; Parent.s = Parent.s + LastParameter.s + "|"
          Word.s = ""
          InArray.b = #True
          ArrayCount.l = 0
        Else
          Word.s = Word.s + Char.s
        EndIf
      
      Case "]"
        If InString.b = #False
          LastValue.s = Word.s
          Word.s = ""
          If InArray.b = #True: ArrayCount.l = ArrayCount.l + 1: EndIf
          If InArray.b = #False
            ;Debug "-> " + Parent.s + LastParameter.s + ": " + LastValue.s
            jsonMapPut(Parent.s + LastParameter.s, LastValue.s)
          Else
            ;Debug "-> " + Parent.s + LastParameter.s + "|" + Str(ArrayCount.l) + ": " + LastValue.s
            jsonMapPut(Parent.s + LastParameter.s + "|" + Str(ArrayCount.l), LastValue.s)
          EndIf
        Else
          Word.s = Word.s + Char.s
        EndIf
        InArray.b = #False
      
      Case "\"
        x.l = x.l + 1
        Char.s = Mid(JSONString.s, x.l, 1)
        Select Char.s
          Case Chr(34): Word.s = Word.s + Chr(34)
          Case "\": Word.s = Word.s + "\"    ; backslash
          Case "/": Word.s = Word.s + "/"    ; slash
          Case "b": Word.s = Word.s + Chr(8) ; backspace
          Case "f": Word.s = Word.s + Chr(14); formfeed
          Case "n": Word.s = Word.s + #LF$   ; new line
          Case "r": Word.s = Word.s + #CR$   ; carriage return
          Case "t": Word.s = Word.s + Chr(9) ; tabulator
          Case "u" ; hex 4 digits
            x.l = x.l + 1
            Char1.s = Mid(JSONString.s, x.l, 2)
            x.l = x.l + 2
            Char2.s = Mid(JSONString.s, x.l, 2)
            x.l = x.l + 2
            Word.s = Word.s + Chr(jsonHex2Dec(Char1.s)) + Chr(jsonHex2Dec(Char2.s))
        EndSelect
            
      Case " "
        If InString.b = #True
          Word.s = Word.s + Char.s
        EndIf
        
      Default
        Word.s = Word.s + Char.s
        
    EndSelect
  Next
EndProcedure

; ######################
; ###    TEST CODE   ###
; ######################

JSON.s = "{" + Chr(34) + "Kreditkarte" + Chr(34) + " : " + Chr(34) + "Xema" + Chr(34) + "," + Chr(34) + "Nummer" + Chr(34) + "      : " + Chr(34) + "1234-5678-9012-3456" + Chr(34) + "," + Chr(34) + "Inhaber" + Chr(34) + "       : {" + Chr(34) + "Name" + Chr(34) + "        : " + Chr(34) + "Reich" + Chr(34) + "," + Chr(34) + "Vorname" + Chr(34) + "     : " + Chr(34) + "Rainer" + Chr(34) + "," + Chr(34) + "Geschlecht" + Chr(34) + "  : " + Chr(34) + "männlich" + Chr(34) + "," + Chr(34) + "Vorlieben" + Chr(34) + "   : [ " + Chr(34) + "Reiten" + Chr(34) + ", " + Chr(34) + "Schwimmen" + Chr(34) + ", " + Chr(34) + "Lesen" + Chr(34) + " ]," + Chr(34) + "Alter" + Chr(34) + "       : null}," + Chr(34) + "Deckung" + Chr(34) + "     : 2e+6," + Chr(34) + "Währung" + Chr(34) + "     : " + Chr(34) + "EURO" + Chr(34) + "}"

jsonDecode(JSON.s)

Debug "Direct Access:"
Debug "Nummer: " + jsonMapGet("Nummer")
Debug "Vorname: " + jsonMapGet("Inhaber|Vorname")
Debug "Vorliebe 2" + jsonMapGet("Inhaber|Vorlieben|2")
Debug " "
Debug "Array content:"
ForEach jsonMap()
  Debug jsonMap()\Index + ": " + jsonMap()\Value
Next
Man kann einfach über einen String auf die gewünschten Objekte und Daten zugreifen. Arrays werden mit 1 beginnend durchnumeriert.

Was dieser Code (noch) nicht kann:
- kein True, False (wird als String geliefert)
- kein "null" (wird als String geliefert)
- Keine Befehle um Arrays durchzulaufen oder deren Anzahl zu ermitteln etc.

In meinem Fall ist das absolut ausreichend. Wer Lust verspürt, kann das ja entsprechend erweitern und hier posten.

[EDIT 12.10.2009] verbessertes Handling von Arrays. Rückgabe von Strings in Anführungszeichen zur Unterscheidung (kann man ja dann rausfiltern).
[EDIT 04.02.2011] unterstützt jetzt auch die Zeichen : [ ] { } in Namen und Werten. Kleinen Bug rausgenommen.

Grüsse,

Volker
Zuletzt geändert von Kukulkan am 04.02.2011 12:34, insgesamt 4-mal geändert.
Benutzeravatar
milan1612
Beiträge: 810
Registriert: 15.04.2007 17:58

Re: Wie JSON dekodieren?

Beitrag von milan1612 »

Na dann ist ja alles klar :allright:
Bin nur noch sehr selten hier, bitte nur noch per PN kontaktieren
Benutzeravatar
Kukulkan
Beiträge: 1066
Registriert: 09.09.2004 07:07
Wohnort: Süddeutschland
Kontaktdaten:

Re: Wie JSON dekodieren?

Beitrag von Kukulkan »

Ich hab den obendran geposteten JSON Parse Code nochmal verbessert (besseres Handling von Arrays und Objekten). Er ist recht gut und kann mit vielen JSON Dateien sauber umgehen.

Grüsse,

Volker
Benutzeravatar
Kukulkan
Beiträge: 1066
Registriert: 09.09.2004 07:07
Wohnort: Süddeutschland
Kontaktdaten:

Re: Wie JSON dekodieren?

Beitrag von Kukulkan »

Hab den obigen Code nochmal aktualisiert.

Das ist neu:
- Fehler durch zu viele Werte am Ende der JSON Datei sind behoben.
- In den Texten (Werten) und Namen dürfen nun auch die Zeichen : [ ] { } enthalten sein.
- Einträge mit leerem Wert werden nicht mehr gespeichert (ein Zugriff liefert ja aber wieder leer).

Grüße,

Kukulkan
Antworten