Aktuelle Zeit: 18.09.2019 07:39

Alle Zeiten sind UTC + 1 Stunde [ Sommerzeit ]




Ein neues Thema erstellen Auf das Thema antworten  [ 6 Beiträge ] 
Autor Nachricht
 Betreff des Beitrags: Problem mit verschachtelten JSON Daten
BeitragVerfasst: 10.04.2019 23:46 
Offline
Benutzeravatar

Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg
Hallo zusammen,

ich wollte mir "mal kurz" ein Tool zur Extraktion von Tankstellenpreisen aus einer JSON-Datei zusammenschreiben, aber dieses JSON Format macht mich echt fertig. Bis vor 1 Stunde habe ich noch nie was von JSON gehört, geschweige denn etwas damit gemacht.

Soweit ich das verstanden habe liegt mir hier eine JSON Datei vor, die in der äußeren "Schicht" aus einem Array mit 10 Einträgen besteht. Jeder Arrayeintrag entspricht einer Tankstelle und besteht selbst wieder aus diversen Untereinträgen. Viele dieser Untereinträge sind "flat", das heißt ich kann sie relativ einfach mit GetJSONMember() extrahieren - entsprechenden Code habe ich mir auf die Schnelle aus der PB-Hilfe zusammengestoppelt. Einige Untereinträge sind selbst aber wieder Arrays und genau da habe ich es bisher nicht geschafft dran zu kommen. Ich weiß nicht mit welchen JSON-Funktionen ich mich zu dem betreffenden Eintrag "durch hangeln" muss. Die Hilfe liefert leider keine Beispiele für Zugriff auf verschachtelte Arrays in einem JSON String.

Ich liefere hier mal nur den ersten Arrayeintrag aus der Datei, sonst wird es zu lang. Ein Eintrag reicht ja auch völlig aus.
Folgende Daten müsstet ihr als Textdatei "spritpreis-json-stripped.txt" speichern:

Code:
{"id":41188,"name":"Diskont Tankstelle","location":{"address":"Neue Landstraße 74 (\"Hofer-Parkplatz\")","postalCode":"4655","city":"Vorchdorf","latitude":48.008358,"longitude":13.920214},"contact":{"telephone":"43800202055","mail":"office@fe-trading.at","website":"http://www.diskonttanken.at/"},"openingHours":[{"day":"MO","label":"Montag","order":1,"from":"00:00","to":"24:00"},{"day":"DI","label":"Dienstag","order":2,"from":"00:00","to":"24:00"},{"day":"MI","label":"Mittwoch","order":3,"from":"00:00","to":"24:00"},{"day":"DO","label":"Donnerstag","order":4,"from":"00:00","to":"24:00"},{"day":"FR","label":"Freitag","order":5,"from":"00:00","to":"24:00"},{"day":"SA","label":"Samstag","order":6,"from":"00:00","to":"24:00"},{"day":"SO","label":"Sonntag","order":7,"from":"00:00","to":"24:00"},{"day":"FE","label":"Feiertag","order":8,"from":"00:00","to":"24:00"}],"offerInformation":{"service":false,"selfService":true,"unattended":true},"paymentMethods":{"cash":false,"debitCard":true,"creditCard":true,"others":"DKV-Card"},"paymentArrangements":{"cooperative":false,"clubCard":false},"position":1,"open":true,"distance":12.111741463692725,"prices":[{"fuelType":"DIE","amount":1.146,"label":"Diesel"}]}


Hier nun der Code mit dem ich erfolgreich auf das Member, das Element, den Eintrag (oder wie auch immer es in JSON richtig benannt wird) "id" und "name" in der ersten Zeile zu greifen kann. Jedoch ist mir der Versuch auf den Eintrag "address" innerhalb des Arrays "location" zuzugreifen bisher nicht geglückt. Ihr müsstet den Laufwerksbuchstaben und ggf. den Dateinamen in der ersten Zeile anpassen:
Code:
If ReadFile(0, "D:\spritpreis-json-stripped.txt")
   sJsonFile.s = ReadString(0)
   CloseFile(0)
Else
   Debug "ReadFile Error"
EndIf

If ParseJSON(0, sJSONFile)
   
   For i = 0 To JSONArraySize(JSONValue(0)) - 1
      Debug Str( GetJSONInteger(GetJSONMember(GetJSONElement(JSONValue(0), i), "id")) )
      Debug GetJSONString(GetJSONMember(GetJSONElement(JSONValue(0), i), "name"))
      Debug GetJSONString(GetJSONMember(GetJSONElement(GetJSONMember(GetJSONElement(JSONValue(0), 0), "address"), i), "location"))
      ;[{"id":41188,"name":"Diskont Tankstelle","location":{"address":"Neue Landstraße 74
   Next i
   FreeJSON(0)
Else
   Debug "JSON Parse Error"
EndIf


Debugoutput sagt:
Zitat:
[23:30:04] [Debug] 41188
[23:30:04] [Debug] Diskont Tankstelle
[23:30:04] [ERROR] Zeile: 13
[23:30:04] [ERROR] Ungültiger JSON Wert. (0)


Wenn mich hierzu jemand in die richtige Richtung schubsen könnte, wär das super. Vermutlich habe ich einfach nur das Konzept JSON noch nicht ganz verstanden. Es soll ja lt. Beschreibung ein "ganz einfaches" Datenaustauschformat sein, was sehr leicht von Mensch und Maschine zu lesen ist.

Gruß Kurzer

Edit: JSON-Datei nach Hinweis von Bisonte korrigiert.

_________________
"Never run a changing system!"
PB 5.62, OS: Windows 7 Pro x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Ich bin Baujahr 1968, also aktuell 51.


Zuletzt geändert von Kurzer am 11.04.2019 09:32, insgesamt 1-mal geändert.

Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: Problem mit verschachtelten JSON Daten
BeitragVerfasst: 11.04.2019 00:31 
Offline
Benutzeravatar

Registriert: 01.04.2007 20:18
Ich benutze in so einem Fall (es sind ja extrem viele Werte die da aufschlagen) ExtractJSONStructure.

Dein Beispiel JSON ist leider nicht korrekt. die Eckige Klammer am Anfang und am Ende muss entfernt werden,
damit es in deinem "Einzelbeispiel" funktioniert. (Ich nehme an, im Original ist es eine Liste...)

Soweit so gut. Es ist etwas Vorarbeit nötig, um die Daten in die richtigen Strukturen zu pressen.
Ich hab da mal was vorbereitet :
Code:
Structure s_location
  address.s
  postalCode.s
  city.s
  latitude.f
  longitude.f
EndStructure
Structure s_contact
  telephone.s
  mail.s
  website.s
EndStructure
Structure s_openingItem
  Day.s
  Label.s
  order.i
  from.s
  To.s
EndStructure
Structure s_offerinfo
  service.i
  selfservice.i
  unattended.i
EndStructure
Structure s_paymentMethods
  cash.i
  debitcard.i
  creditcard.i
  others.s
EndStructure
Structure s_paymentArrangements
  cooperative.i
  clubcard.i
EndStructure
Structure s_prices
  fueltype.s
  amount.f
  label.s
EndStructure

Structure json_tankstelle
  ID.i
  Name.s
  Location.s_location
  Contact.s_contact
  List openingHours.s_openingItem()
  offerInformation.s_offerinfo
  paymentMethods.s_paymentMethods
  paymentArrangements.s_paymentArrangements
  position.i
  open.i
  distance.f
  List prices.s_prices()
EndStructure

Tank.json_tankstelle

jSon = LoadJSON(#PB_Any, "D:\spritpreis-json-stripped.txt", #PB_JSON_NoCase)

If jSon
 
  ExtractJSONStructure(JSONValue(jSon), @Tank, json_tankstelle)
  FreeJSON(jSon)
 
EndIf

Debug Tank\ID
Debug Tank\Name
Debug Tank\Location\address
Debug Tank\Location\postalCode
Debug Tank\Location\city
Debug StrF(Tank\Location\latitude, 6)
Debug StrF(Tank\Location\longitude, 6)

Debug Tank\Contact\telephone
Debug Tank\Contact\mail
Debug Tank\Contact\website

ForEach Tank\openingHours()
  With Tank\openingHours()
    Debug \Day
    Debug \Label
    Debug \order
    Debug \from
    Debug \To
  EndWith
Next

ForEach Tank\prices()
  With Tank\prices()
    Debug \fueltype
    Debug StrF(\amount, 3)
    Debug \label
  EndWith
Next


Sieht auf den ersten Blick wie die Hölle aus, ist aber beim Benutzen eine echte Wohltat, weil alles da ist wo man es braucht ;)

_________________
PureBasic 5.70 LTS (Windows x86/x64) | Windows10 Pro x64 | Z370 Extreme4 | i7 8770k | 32GB RAM | iChill GeForce GTX 980 X4 Ultra | HAF XF Evo​​


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: Problem mit verschachtelten JSON Daten
BeitragVerfasst: 11.04.2019 09:31 
Offline
Benutzeravatar

Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg
Wow! Vielen Dank Bisonte. :allright: Nee, sieht nicht wie die Hölle aus, ist extrem strukturiert und ich kann das sofort leicht nachvollziehen. Den ganzen "Drecksjob" macht ja ExtractJSONStructure(). :lol:

ExtractJSONStructure() ist ja echt ein mächtiger Befehl. Dein Code klappt hervorragend. Vielen Dank für die ganze Tipparbeit, die du dir gemacht hast. Ein virtuelles Bier/Kaffee/Cola/... von mir ist dir sicher. :praise:

Zwei Fragen habe ich dazu noch (nur am Rande):
1) Werden JSON Objekte in einer Struktur immer als Liste repräsentiert? Ich habe dazu keinen Hinweis in der Hilfe gefunden und wenn ich versuche aus...
Code:
List prices.s_prices()

das hier zu machen:
Code:
prices.s_prices[1]

(weil es wird eh immer nur eine Sorte mit einem Preis in der JSON Datei vorkommen), dann wird das statische Array von ExtractJSONStructure() nicht gefüllt.

2) Wenn man den Wert "address" zu Fuß ermitteln wollte ohne die ganze Struktur zu extrahieren, wie müsste das dann korrekt aussehen? Das ist der Teil an dem ich nicht weiter kam.

Gruß Kurzer

Edit: Punkt 2) habe ich mir dank JSONTYpe() (zum Untersuchen was für ein Typ zurück kommt) nun selbst beantwortet.
Die Zeile muss wie folgt lauten:
Code:
Debug GetJSONString(GetJSONMember(GetJSONMember(GetJSONElement(JSONValue(0), i), "location"), "address"))

_________________
"Never run a changing system!"
PB 5.62, OS: Windows 7 Pro x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Ich bin Baujahr 1968, also aktuell 51.


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: Problem mit verschachtelten JSON Daten
BeitragVerfasst: 11.04.2019 17:48 
Offline
Benutzeravatar

Registriert: 01.04.2007 20:18
Kurzer hat geschrieben:
Zwei Fragen habe ich dazu noch (nur am Rande):
1) Werden JSON Objekte in einer Struktur immer als Liste repräsentiert? Ich habe dazu keinen Hinweis in der Hilfe gefunden und wenn ich versuche aus...
Code:
List prices.s_prices()

das hier zu machen:
Code:
prices.s_prices[1]

(weil es wird eh immer nur eine Sorte mit einem Preis in der JSON Datei vorkommen), dann wird das statische Array von ExtractJSONStructure() nicht gefüllt.


Arrays sind in JSON auch als solche zu erkennen.
In deinem Beispiel ist ein Array nicht vorhanden. Bei Dir sind aber Listeneinträge vorhanden (oder solche die es werden könnten, wenn mehr Einträge da wären)

Kurzer hat geschrieben:
2) Wenn man den Wert "address" zu Fuß ermitteln wollte ohne die ganze Struktur zu extrahieren, wie müsste das dann korrekt aussehen? Das ist der Teil an dem ich nicht weiter kam.
Gruß Kurzer


Das ist beinahe wie bei XML.
Root\location\adress
Root\location\postalcode
usw.
Zu Fuss wäre das in etwa so :
Code:
jSon = LoadJSON(#PB_Any, "D:\spritpreis-json-stripped.txt", #PB_JSON_NoCase)

If jSon
  jV = JSONValue(jSon)
  If jV
    Node_Location = GetJSONMember(jV, "location")
   
    If Node_Location
      Debug GetJSONString(GetJSONMember(Node_Location, "address"))
      Debug GetJSONString(GetJSONMember(Node_Location, "postalcode"))
      ; usw...
    EndIf
   
  EndIf
   
  FreeJSON(jSon)
 
EndIf

_________________
PureBasic 5.70 LTS (Windows x86/x64) | Windows10 Pro x64 | Z370 Extreme4 | i7 8770k | 32GB RAM | iChill GeForce GTX 980 X4 Ultra | HAF XF Evo​​


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: Problem mit verschachtelten JSON Daten
BeitragVerfasst: 11.04.2019 18:32 
Offline
Moderator
Benutzeravatar

Registriert: 15.06.2008 18:22
Wohnort: Dresden
Zu 2.: Für einfachen Zugriff auf JSON-Daten wie bei XMLNodeFromPath() kann ich außerdem empfehlen:
(angepasstes Original von Little John hier)

Code:
; #author Little John, derivated by hgzh
; #url    http://www.purebasic.fr/english/viewtopic.php?p=467733#p467733

DeclareModule JSON
   Declare.s Get (jn.i, path$, quote$="")
   Declare.i Set (jn.i, path$, value$, quote$="'")
EndDeclareModule

Module JSON
   EnableExplicit
   
   Procedure.i _GetJSONValue (jn.i, path$)
      ; -- internal function
      Protected jv.i, depth.i, level.i, index.i, field$
     
      jv = JSONValue(jn)
     
      If jv <> 0 And path$ <> ""
         depth = CountString(path$, "\") + 1
         For level = 1 To depth
            field$ = StringField(path$, level, "\")
            If Left(field$, 1) = "[" And Right(field$, 1) = "]"
               index = Val(Mid(field$, 2, Len(field$)-2))
               If JSONType(jv) = #PB_JSON_Array And index >= 0 And index < JSONArraySize(jv)
                  jv = GetJSONElement(jv, index)
                  If jv = 0
                    ProcedureReturn 0
                  EndIf
               Else
                  jv = 0
               EndIf
            Else   
               If JSONType(jv) = #PB_JSON_Object
                  jv = GetJSONMember(jv, field$)   ; 0 if the given 'field$' does not exist in the object
                  If jv = 0
                    ProcedureReturn 0
                  EndIf
               Else
                  jv = 0
               EndIf
            EndIf
         Next   
      EndIf
     
      ProcedureReturn jv
   EndProcedure
   
   
   Procedure.s Get (jn.i, path$, quote$="")
      ; in : jn    : JSON number, e.g. generated by ParseJSON()
      ;      path$ : path to the JSON element that is to be retrieved
      ;      quote$: character that is regarded as quote
      ; out: value of the desired element
      Protected jv.i, ret$=""
     
      If jn
         jv = _GetJSONValue(jn, path$)
         If jv
            Select JSONType(jv)
               Case #PB_JSON_String
                  ret$ = quote$ + GetJSONString(jv) + quote$
               Case #PB_JSON_Boolean
                  If GetJSONBoolean(jv) = #True
                     ret$ = "true"
                  Else
                     ret$ = "false"
                  EndIf   
               Case #PB_JSON_Null
                  ret$ = "null"
               Case #PB_JSON_Number
                  ret$ = StrD(GetJSONDouble(jv))
               Case #PB_JSON_Array
                  ret$ = Str(jv)
            EndSelect     
         EndIf
      EndIf
     
      ProcedureReturn ret$
   EndProcedure
   
   
   Macro _IsNumber (_string_)
      ; -- internal macro
      Bool(_string_ <> "" And (Val(_string_) <> Val(_string_+"1") Or ValF(_string_) <> ValD(_string_+"1")))
   EndMacro
   
   Procedure.i Set (jn.i, path$, value$, quote$="'")
      ; in : jn    : JSON number, e.g. generated by ParseJSON()
      ;      path$ : path to the JSON element that is to be changed
      ;      value$: new value of the desired element
      ;      quote$: character that is regarded as quote
      ; out: #True on success, #False on error
      Protected jv.i, ret.i=#False
     
      If jn
         jv = _GetJSONValue(jn, path$)
         If jv
            If Left(value$, 1) = quote$ And Right(value$, 1) = quote$
               SetJSONString(jv, Mid(value$, 2, Len(value$)-2))
               ret = #True
            ElseIf value$ = "true"
               SetJSONBoolean(jv, #True)
               ret = #True
            ElseIf value$ = "false"
               SetJSONBoolean(jv, #False)
               ret = #True
            ElseIf value$ = "null"
               SetJSONNull(jv)
               ret = #True
            ElseIf _IsNumber(value$)
               SetJSONDouble(jv, ValD(value$))
               ret = #True
            EndIf
         EndIf
      EndIf
     
      ProcedureReturn ret
   EndProcedure
EndModule

_________________
Win10 x64 | PB 5.70 (x86 und x64)


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: Problem mit verschachtelten JSON Daten
BeitragVerfasst: 11.04.2019 23:00 
Offline
Benutzeravatar

Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg
Besten Dank für Eure Hinweise. Die JSON-Extraktion verstehe ich nun besser.

Allerdigs stolpere ich bei der weiteren Programmierung über eine komische Sache im Bereich ReceiveHTTPMemory().
Ich weiß nicht, ob es ein Problem der Webseite ist von der ich Daten lese oder ein Problem von PB. Das geschilderte Verhalten tritt mit PB 5.62 und 5.70 auf. Andere Versionen habe ich nicht probiert.

Folgender Testcode greift von e-control.at per ReceiveHTTPMemory() eine JSON Datei mit Tankstellen und deren Benzinpreisen ab.
Es werden zwei identische Abfragen hintereinander ausgeführt. Bei den Abfragen sind nur die URLs verschieden. Die eine URL enthält das Token DIE für Diesel, das andere SUP für Super. Beide Abfragen kann man mit dem "If 1=1" entweder aktivieren oder deaktivieren.

Das Ergebnis der letzten Abfrage wird in die Zwischenablage kopiert, damit man sich den JSON String zur Ansicht in einen Texteditor einfügen kann.

Folgendes Problem:

Bei der unten dargestellten Konstellation (beide Abfragen ausführen, Diesel zuerst) wird beim Empfang des zweiten JSON Strings (also den für Super) am ende Müll mit übertragen. Das Ende der JSON-Datei sieht so aus:
Code:
"distance":12.812155275720695,"prices":[]}]se,"clubCard

Es müsste aber so aussehen:
Code:
"distance":12.812155275720695,"prices":[]}]

Dies tritt nur auf, wenn ich beide Abfragen ausführe und Diesel zuerst abgefragt wird. Frage ich Super zuerst ab und dann Diesel, dann sind beide JSON Strings in Ordnung. Ebenso, wenn ich Super oder Diesel einzeln abfrage.

Ich vermute ja, dass die Webseite der Verursacher ist, bin mir aber nicht sicher. Oder habe ich was in meinem Testcode übersehen?

Code:
InitNetwork()

; Diesel
If 1=1
   ;sUrl.s = "https://api.e-control.at/sprit/1.0/search/gas-stations/by-address?latitude=47.902266&longitude=13.956367&fuelType=SUP&includeClosed=false"
   sUrl.s = "https://api.e-control.at/sprit/1.0/search/gas-stations/by-address?latitude=47.902266&longitude=13.956367&fuelType=DIE&includeClosed=false"
   *ReceiveBuffer = ReceiveHTTPMemory(sUrl)
   If *ReceiveBuffer
      sResult.s = PeekS(*ReceiveBuffer, MemorySize(*ReceiveBuffer), #PB_UTF8)
      FreeMemory(*ReceiveBuffer)
   Else
      Debug "Error"
   EndIf
EndIf

sResult = ""

; Super
If 1=1
   sUrl.s = "https://api.e-control.at/sprit/1.0/search/gas-stations/by-address?latitude=47.902266&longitude=13.956367&fuelType=SUP&includeClosed=false"
   ;sUrl.s = "https://api.e-control.at/sprit/1.0/search/gas-stations/by-address?latitude=47.902266&longitude=13.956367&fuelType=DIE&includeClosed=false"
   *ReceiveBuffer = ReceiveHTTPMemory(sUrl)
   If *ReceiveBuffer
      sResult + PeekS(*ReceiveBuffer, MemorySize(*ReceiveBuffer), #PB_UTF8)
      FreeMemory(*ReceiveBuffer)
   Else
      Debug "Error"
   EndIf
EndIf

SetClipboardText(sResult)


Gruß Kurzer

Edit: Wenn ich #PB-Ascii statt #PB_UTF8 beim Peek nutze, dann klappts. Sehr ominös.
Code:
PeekS(*ReceiveBuffer, MemorySize(*ReceiveBuffer), #PB_Ascii)


Edit2: So jetzt aber: Es fehlte das |#PB_ByteLength. Steht ja auch so als Beispiel in der Hilfe, ich hätte nur mal reingucken müssen. Na ja, ich tippe offenbar gern Monologe... aber wenn's hilft ist's ja ok. :lol:
Code:
PeekS(*ReceiveBuffer, MemorySize(*ReceiveBuffer), #PB_UTF8|#PB_ByteLength)

_________________
"Never run a changing system!"
PB 5.62, OS: Windows 7 Pro x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520
Ich bin Baujahr 1968, also aktuell 51.


Nach oben
 Profil  
Mit Zitat antworten  
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 6 Beiträge ] 

Alle Zeiten sind UTC + 1 Stunde [ Sommerzeit ]


Wer ist online?

Mitglieder in diesem Forum: Google [Bot] und 5 Gäste


Sie dürfen keine neuen Themen in diesem Forum erstellen.
Sie dürfen keine Antworten zu Themen in diesem Forum erstellen.
Sie dürfen Ihre Beiträge in diesem Forum nicht ändern.
Sie dürfen Ihre Beiträge in diesem Forum nicht löschen.

Suche nach:
Gehe zu:  

 


Powered by phpBB © 2008 phpBB Group | Deutsche Übersetzung durch phpBB.de
subSilver+ theme by Canver Software, sponsor Sanal Modifiye