![]() |
Problem bei JSON-Abfrage
der Einsicht folgend, dass mein Programm zum Übernehmen von Metadaten von Spotify via HTML - Sources vom Design her in eine Sackgasse geraten ist,
![]() Beispiel:
Delphi-Quellcode:
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?
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; Gruß LP |
AW: Problem bei JSON-Abfrage
Versuch mal, ob das den Fehler (zumindest an der Stelle) behebt:
Delphi-Quellcode:
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.
if Assigned(TJSONObject(Value).GetValue('name')) then
PlName := TJSONObject(Value).GetValue('name').Value; [edit] Oder man benutzt die generische Funktion TryGetValue<T> von TJSONObject, das ist etwas bequemer.
Delphi-Quellcode:
[/edit]
if not TJSONObject(Value).TryGetValue<string>('name', PlName) then
PlName := ''; |
AW: Problem bei JSON-Abfrage
Zitat:
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; Zitat:
Das ist ja noch 'cooler'. So funktioniert's tatsächlich:
Delphi-Quellcode:
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.
Function TSpManager.GetValue(value:TJSONValue; key: string): String;
begin if not TJSONObject(Value).TryGetValue<string>(lowercase(key), result) then result := ''; end; Gruß LP |
AW: Problem bei JSON-Abfrage
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.
|
AW: Problem bei JSON-Abfrage
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. |
AW: Problem bei JSON-Abfrage
Liste der Anhänge anzeigen (Anzahl: 2)
Zitat:
Delphi-Quellcode:
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){ 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; Für Anregungen und Verbesserungsvorschläge bin ich ausgesprochen dankbar! Gruß LP |
AW: Problem bei JSON-Abfrage
Ich baue mir da immer anhand der API passende Objektstrukturen zusammen. Dann wird der Abruf nicht nur wesentlich simpler, man bekommt auch gleich die Typsicherheit dazu. Ich habe hier nur einen Teil der Felder deklariert. Alle übrigen werden ignoriert. Das lässt sich relativ leicht erweitern.
Delphi-Quellcode:
Der eigentliche Abrufcode beschränkt auf die im Eingangspost gezeigten Daten sieht dann so aus:
uses
System.JSON, REST.Json; type TArtist = class private FName: string; public property Name: string read FName; end; TAlbum = class private FArtists: TArray<TArtist>; FName: string; public property Artists: TArray<TArtist> read FArtists; property Name: string read FName; end; TTrack = class private FAlbum: TAlbum; FArtists: TArray<TArtist>; FId: string; FName: string; public property Album: TAlbum read FAlbum; property Artists: TArray<TArtist> read FArtists; property Id: string read FId; property Name: string read FName; end; TUser = class private FDisplay_name: string; public property Display_name: string read FDisplay_name; end; TPlaylistTrack = class private FAdded_at: TDateTime; FAdded_by: TUser; FIs_local: Boolean; FTrack: TTrack; public property Added_at: TDateTime read FAdded_at; property Added_by: TUser read FAdded_by; property Is_local: Boolean read FIs_local; property Track: TTrack read FTrack; end; TPaging<T: class> = class private FHref: string; FItems: TArray<T>; FLimit: Integer; FNext: string; FOffset: Integer; FPrevious: string; FTotal: Integer; public property Href: string read FHref; property Items: TArray<T> read FItems; property Limit: Integer read FLimit; property Next: string read FNext; property Offset: Integer read FOffset; property Previous: string read FPrevious; property Total: Integer read FTotal; end; TPlayList = class private FName: string; FTracks: TPaging<TPlaylistTrack>; public property Name: string read FName; property Tracks: TPaging<TPlaylistTrack> read FTracks; end;
Delphi-Quellcode:
function TSpManager.GetPlaylistTracks(PlaylistID: string; List: TStrings): string; // Liste der Track-IDs aus Playlist holen
var playlist: TPlaylist; track: TPlaylistTrack; begin result := ''; RESTRequest1.Resource := 'v1/playlists/{PlaylistID}'; // Accesspoint der Spotify Web-API RESTRequest1.AddParameter('PlaylistID', PlaylistID); RESTRequest1.Execute; if RestResponse1.JSONValue is TJSONObject then begin playlist := TJson.JsonToObject<TPlaylist>(TJSONObject(RESTResponse1.JSONValue)); if playlist <> nil then begin Result := playlist.Name; if playlist.Tracks <> nil then begin for track in playlist.Tracks.Items do begin if track.Track <> nil then begin List.Add(track.Track.Id); end; end; end; end; end; end; |
AW: Problem bei JSON-Abfrage
Zitat:
Gruß LP |
AW: Problem bei JSON-Abfrage
Es sollte nicht unerwähnt bleiben, dass der Code alles andere als vollständig ist. So werden die erzeugten Objekt-Instanzen unter non-ARC Plattformen nicht wieder freigegeben. Das müsste man noch im Destructor ergänzen.
|
AW: Problem bei JSON-Abfrage
Ich mache das übrigens ähnlich, nur dass das bei mir normalerweise keine Klassen, sondern Records sind. Diese verfügen über eine Klassenfunktion FromJSON, welche ein TJSONObject übernimmt und einen befüllten Record zurückgibt.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:39 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz