AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Problem bei JSON-Abfrage

Ein Thema von Maekkelrajter · begonnen am 20. Sep 2020 · letzter Beitrag vom 4. Nov 2020
Antwort Antwort
Maekkelrajter

Registriert seit: 8. Mär 2017
Ort: Köln
124 Beiträge
 
Delphi 11 Alexandria
 
#1

Problem bei JSON-Abfrage

  Alt 20. Sep 2020, 13:13
der Einsicht folgend, dass mein Programm zum Übernehmen von Metadaten von Spotify via HTML - Sources vom Design her in eine Sackgasse geraten ist,(siehe hier) habe ich mich intensiv mit der Entwicklung eines REST-Clients für die Abfrage der Daten über die Spotify Web-Api beschäftigt. Nach mühevoller Einarbeitung mit den spärlich zur Verfügung stehenden Informationen der Delphi - Hilfe und einem einzigen und dazu noch ziemlich unzulänglichen Beispielprogramm bin ich nun tatsächlich soweit, dass alles mehr oder weniger wunschgemäß funktioniert: Anmeldung bzw. Authentifizierung bei Spotify, Laden der Datensätze als JSON-Struktur und schließlich deren Auswertung. Zum Auslesen der Daten benutze ich die von System.JSON zur Verfügung gestellten Methoden.
Beispiel:
Delphi-Quellcode:
Function TSpManager.GetPlaylistTracks(PlaylistID: string; List:TStrings):string; // Liste der Track-IDs aus Playlist holen
  var ResponseValue, subitem,oid,track:TJSONValue;
      oItems: TJSONArray;
      PlName: string;
begin
  result:= '';
  RESTRequest1.Resource:= 'v1/' + 'playlists/' + PlaylistID + '/tracks'; // Accesspoint der Spotify Web-API
  RestRequest1.Execute;
  ResponseValue:= RestResponse1.JSONValue;
  If ResponseValue = NIL Then exit;
  PlName:= TJSONObject(ResponseValue).GetValue('name').Value; // Hier kann es krachen, wenn z.B 'Name' statt 'name' oder wenn 'name' nicht existiert
  oItems := TJSONArray(TJSONObject(ResponseValue).GetValue('items'));
  for subitem in oItems do
  begin
    track:= TJSONObject(subtem).GetValue('track');
    oID:= TJSONObject(track).GetValue('id');
    List.Add(oID.Value);
  end;
  result:= PlName;
end;
Die Beispiel-Funktion liefert in 'List' eine Liste mit den Spotify-IDs aller Tracks der Playlist, mit denen dann die eigentlichen Trackdaten geladen und ausgewertet werden können. Der Rückgabewert ist der Name der Playlist. Das funktioniert, wie gesagt, normalerweise einwandfrei. Wenn aber der gesuchte Schlüssel nicht gefunden wird, wirft System.JSON.TJSONObject.GetValue() eine Accessviolation ('Lesen von Adresse 00000000'). Normalerweise würde ich in einem solchen Fall einen Leerstring als Rückgabewert erwarten, aber keine Exception. Was mache ich da falsch?

Gruß LP

Geändert von Maekkelrajter (20. Sep 2020 um 15:51 Uhr) Grund: Tippfehler ausgemerzt
  Mit Zitat antworten Zitat
Benutzerbild von DeddyH
DeddyH

Registriert seit: 17. Sep 2006
Ort: Barchfeld
27.549 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Problem bei JSON-Abfrage

  Alt 20. Sep 2020, 15:31
Versuch mal, ob das den Fehler (zumindest an der Stelle) behebt:
Delphi-Quellcode:
if Assigned(TJSONObject(Value).GetValue('name')) then
  PlName := TJSONObject(Value).GetValue('name').Value;
Falls ja, könnte man über eine lokale Variable vom Typ TJSONValue nachdenken, die man zuerst befüllt, im Anschluss auf nil prüft und erst danach versucht, den Wert auszulesen.

[edit] Oder man benutzt die generische Funktion TryGetValue<T> von TJSONObject, das ist etwas bequemer.
Delphi-Quellcode:
if not TJSONObject(Value).TryGetValue<string>('name', PlName) then
  PlName := '';
[/edit]
Detlef
"Ich habe Angst vor dem Tag, an dem die Technologie unsere menschlichen Interaktionen übertrumpft. Die Welt wird eine Generation von Idioten bekommen." (Albert Einstein)
Dieser Tag ist längst gekommen

Geändert von DeddyH (20. Sep 2020 um 15:36 Uhr)
  Mit Zitat antworten Zitat
Maekkelrajter

Registriert seit: 8. Mär 2017
Ort: Köln
124 Beiträge
 
Delphi 11 Alexandria
 
#3

AW: Problem bei JSON-Abfrage

  Alt 20. Sep 2020, 16:53
Versuch mal, ob das den Fehler (zumindest an der Stelle) behebt:
Delphi-Quellcode:
if Assigned(TJSONObject(Value).GetValue('name')) then
  PlName := TJSONObject(Value).GetValue('name').Value;
Falls ja, könnte man über eine lokale Variable vom Typ TJSONValue nachdenken, die man zuerst befüllt, im Anschluss auf nil prüft und erst danach versucht, den Wert auszulesen.
Der Fehler wird so tatsächlich behoben, oder besser gesagt, umgangen. Die Idee mit der lokalen Variablen habe ich mit einer Funktion realisiert, gewissermaßen ein Wrapper für den eigentlichen Aufruf von GetValue. Der hilft auch den Code aller übrigen Methoden, die diese Funktion benutzen, deutlich zu vereinfachen:
Delphi-Quellcode:
Function TSpManager.GetValue(value:TJSONValue; key: string): String;
  var jv: TJSONValue;
begin
  result := '';
  jv := TJSONObject(value).GetValue(lowercase(key));
  If jv <> NIL Then result := jv.value;
end;

[edit] Oder man benutzt die generische Funktion TryGetValue<T> von TJSONObject, das ist etwas bequemer.
Delphi-Quellcode:
if not TJSONObject(Value).TryGetValue<string>('name', PlName) then
  PlName := '';

Das ist ja noch 'cooler'. So funktioniert's tatsächlich:
Delphi-Quellcode:
Function TSpManager.GetValue(value:TJSONValue; key: string): String;
begin
  if not TJSONObject(Value).TryGetValue<string>(lowercase(key), result) then
    result := '';
end;
Vielen Dank für die Tipps, aber das alles sind ja eigentlich 'nur' Workarounds. Ich halte es für einen veritablen Bug, wenn in System.JSON intern übergebene Parameter zu einer Accessviolation führen, weil sie nicht auf Validität überprüft werden.

Gruß LP
  Mit Zitat antworten Zitat
Benutzerbild von DeddyH
DeddyH

Registriert seit: 17. Sep 2006
Ort: Barchfeld
27.549 Beiträge
 
Delphi 11 Alexandria
 
#4

AW: Problem bei JSON-Abfrage

  Alt 20. Sep 2020, 16:56
Nunja, GetValue gibt einen TJSONValue zurück, welcher eine Klasse ist. Greift man auf den Wert einer nicht instanzierten Klasse zu, macht es eben Rumms, deshalb sind das keine Workarounds, sondern lediglich defensive Programmierung.
Detlef
"Ich habe Angst vor dem Tag, an dem die Technologie unsere menschlichen Interaktionen übertrumpft. Die Welt wird eine Generation von Idioten bekommen." (Albert Einstein)
Dieser Tag ist längst gekommen
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe
Online

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.090 Beiträge
 
Delphi 12 Athens
 
#5

AW: Problem bei JSON-Abfrage

  Alt 20. Sep 2020, 17:04
Kannst du mal als Beispiel das JSON für eine funktionierende Antwort hier posten? Das müsste noch wesentlich eleganter gehen.

Auch deine aktuelle Lösung für die Authentifizierung würde ich gerne mal sehen (natürlich ohne echte Daten). Da ist oft auch noch Raum für Vereinfachungen.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Maekkelrajter

Registriert seit: 8. Mär 2017
Ort: Köln
124 Beiträge
 
Delphi 11 Alexandria
 
#6

AW: Problem bei JSON-Abfrage

  Alt 20. Sep 2020, 22:33
Kannst du mal als Beispiel das JSON für eine funktionierende Antwort hier posten? Das müsste noch wesentlich eleganter gehen.

Auch deine aktuelle Lösung für die Authentifizierung würde ich gerne mal sehen (natürlich ohne echte Daten). Da ist oft auch noch Raum für Vereinfachungen.
Ich war ja froh, als ich das Ding endlich am laufen hatte. Ich zweifele auch nicht im geringsten daran dass der Code noch verbesserungsfähig ist. Hier also die Authentifizierungs Routinen, die ich übrigens zum Teil aus dem Beispiel-Programm abgekupfert habe:
Delphi-Quellcode:

{
type
    TSpManager = class(TForm)
    [...]
    IdHTTPServer1: TIdHTTPServer;
    OAuth2Authenticator1: TOAuth2Authenticator;
    RESTClient1: TRESTClient;
    RESTRequest1: TRESTRequest;
    RESTResponse1: TRESTResponse;
    procedure IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    [...]
}


Procedure TSpManager.SendData(s:string);
 var
    CDStruct: TCopyDataStruct;
    copytext: ansistring;
begin
  copytext:= s;
  begin
    CDStruct.dwData:= 0;
    CDStruct.cbData:= length(Copytext) + 1;
    CDStruct.lpData:= @CopyText[1];
    SendMessage(MainHandle, WM_COPYDATA,1,integer(@CDStruct));
  end;
end;


Procedure TSpManager.AuthorizeSpotifyAccess;
begin
  IdHTTPServer1.Active := True;
  OAuth2Authenticator1.ResetToDefaults;
  OAuth2Authenticator1.ClientID := Stcf.AccessData.clientID; // Bei der Registrierung von Spotify zugeteilt (lokal gespeichert)
  OAuth2Authenticator1.ClientSecret := Stcf.AccessData.ClientSecret; // Bei der Registrierung von Spotify zugeteilt (lokal gespeichert)
  OAuth2Authenticator1.AuthorizationEndpoint := 'https://accounts.spotify.com/authorize';
  // Bereiche, auf die Benutzerzugriff erlaubt sein soll
  OAuth2Authenticator1.Scope := 'playlist-read-private user-read-private user-library-read';
  // Weiterleitungsseite mit dem aktuellen AUTORISIERUNGSCODE
  OAuth2Authenticator1.RedirectionEndpoint := 'http://localhost:9090';
  // Die Definition des URI zum Generieren des AUTORISIERUNGSCODES
  ShellExecute(0, 'OPEN', PChar(Self.OAuth2Authenticator1.AuthorizationRequestURI), '', '', SW_SHOWNORMAL);
end;


procedure TSpManager.IdHTTPServer1CommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  var s:string;
begin
  // Abfrage-String validiert
  if (ARequestInfo.Params.IndexOfName('code') = -1) then Exit;
  // Definition des ACCESS CODE, den wir von der Weiterleitungsseite abrufen
  OAuth2Authenticator1.AuthCode := ARequestInfo.Params.Values['code'];
  // End-point für die Generierung des ACCESS TOKEN
  OAuth2Authenticator1.AccessTokenEndpoint := 'https://accounts.spotify.com/api/token';
  // Definieren des ACCESS TOKEN aus dem AUTORISIERUNGSCODE
  OAuth2Authenticator1.ChangeAuthCodeToAccesToken;
  // Antwort an den Browser
  AResponseInfo.ContentText := '<html><body><script language=javascript>window.close();</script>Vielen Dank, dass Sie den Zugriff erlaubt haben!</body></html>';
  // Operation erfolgreich!
  s:= OAuth2Authenticator1.AccessToken;
  SendData(s); // Schickt die Daten nur zur Information an das Hauptfenster;
  FAuthorized:= s <> ''; // Boolean als Flag
end;
Im Anhang ein JSON mit Playlist-Daten, das Spotify nach einem Request mit dem accesspoint '/v1/playlists/{playlist_id}' liefert. Für meine Zwecke werden nur wenige Daten ausgelesen und in Zieldateien geschrieben (wenn vorhanden)
Für Anregungen und Verbesserungsvorschläge bin ich ausgesprochen dankbar!

Gruß LP
Angehängte Grafiken
Dateityp: jpg Screenshot200920_2.jpg (123,2 KB, 86x aufgerufen)
Angehängte Dateien
Dateityp: txt RESTResponse_Content.txt (31,8 KB, 28x aufgerufen)

Geändert von Maekkelrajter (20. Sep 2020 um 22:41 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:40 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz