![]() |
Rückgabe-Typ anpassen?
Hallo,
ich arbeite gerade an einem JSON-Parser. Dies wäre eigentlich kein Problem, wenn ich nicht verschiedene Datentypen einbauen wollen würde :) Hier ein kommentiertes Beispiel aus wiki:
Code:
Wie kann ich das am besten Lösen, dass ich alle Werte in der selben Klasse speichern kann, aber einen anderen Typen abspeichere?
{
"id": 1, // integer "name": "Foo", // string "price": 123, // integer "tags": [ // array "Bar", // array "Eek" // array ], "stock": { "warehouse": 300, // string "retail": 20 // string } } Freundliche Grüsse |
AW: Rückgabe-Typ anpassen?
Ähm, du willst einen JSON-Parser erstellen oder benutzen?
Eigentlich gibt es schon genug JSON-Parser für Delphi ... Im Übrigen sind die Kommentare nicht ganz korrekt:
Code:
Zwingende Basis-Lektüre ist hier auch
{
"id": 1, // integer "name": "Foo", // string "price": 123, // number "tags": [ // array "Bar", // array-item string "Eek" // array-item string ], "stock": { // object "warehouse": 300, // number "retail": 20 // number } } ![]() |
AW: Rückgabe-Typ anpassen?
Zitat:
[add] Zitat:
True und False als einzelne Objekte :wall: [/add] Zitat:
Variant und TValue können mehrere "Typen" speichern oder du reagierst auf den Typen und speicherst dann den "Wert" in einer entsprechenden Variable, ähnlich einem varianten Array. > String, Double, Array/Objekt, ... Aber warum schaust du nicht einfach bei anderen Delphi-JSON-Libs ab? z.B. ![]() (ich weiß jetzt nicht, welche Lib Delphi integriert hat) Warum baut eigentlich jeder einen JSON-Parser? :stupid: *hust* ![]() Ich hab mich entschieden die "Werte" als String zu speichern. (im Prinzip ist es im JSON dann eh alles ein String) Wobei ich keinen JSON-Parser bastel (OK, der TJSONReader ist eine Art SAXParser), sondern ein echtes DOM, welches vollständig bearbeitbar ist. Das Projekt war aber eigentlich entstanden, um die Speicherverwaltung im Multiplattform an einem praktischen Beispiel zu testen. Siehe die vielen Tickets ala ![]() Es gibt dazu dbald auch mal einen Artikel über JSON, JavaScript, Delphi, wie das drüben ist, also im JavaScript und vielleicht auch anderswo, und warum das JavaScript-Objekt-Model-Prinzip (nur serialisieren und deserialisieren) nicht unbedingt mit Delphi kompatibel ist. |
AW: Rückgabe-Typ anpassen?
Warum sollte da was mit Delphi nicht kompatibel sein? :gruebel:
Wenn dem so ist, dann ist Delphi auch nicht mit XML bis hin zu einem Binär-Daten-Format kompatibel (ja, serialisieren). |
AW: Rückgabe-Typ anpassen?
Weil Delphi keine dynamischen Typen erlaubt?
'ne Variable, wo wirklich alles rein passt und das beliebig verschachtelbar ... das kennt Pascal nativ erstmal nicht. Ein Variant kann zwar einfache Typen und Arrays enthalten und Objekte, aber nativ keine benamten Objekte, außer man klemmt z.B. ein Dictionary dazwschen. In JavaScript kann man JSON einfach so deserialisieren und JS baut dann dynmaisch ein Objekt auf, Anhand der gefundenen "Property"-Namen. Dort ist es eben möglich zur Laufzeit das Objekt umzubauen, was in einer statischen Sprache, wie z.B. Delphi, einfach nicht möglich ist, außer man baut sich dort entsprechende Wrapper-Klassen, so wie sie in fast jedem JSON-Parser enthalten sind. Entweder man muß der JSON-Deserialisierung geziehlt ein Objkekt/Klasse geben, was schon vorher die nötige Struktur besitzt, oder man baut eben Datenhaltungsklassen in den Baum (ala TJsonObject oder TJsonValuse, wie sie meistens heißen). |
AW: Rückgabe-Typ anpassen?
Ja, das gleiche Problem hat man mit XML, CSV, Ini-Dateien, ... Binär-Daten.
Aber Java scheint ja eine Zaubersprache zu sein. Da kann man einfach so irgendein JSON rüberschicken und schwupps kann die Anwendung sofort das korrekt interpretieren? Oder muss selbst bei Java der Kontext klar sein? Was bringt mir die dynamische Instanz, wenn die Anwendung die Eigenschaft "wuppdi" nicht kennt und somit nicht verarbeiten kann? Ich würde mal sagen: Nichts. Denn die statische Klasse ohne
Delphi-Quellcode:
Eigenschaft ist genauso gut/schlecht wie die dynamische Klasse mit der Eigenschaft, die aber von der Anwendung nicht berücksichtigt wird.
wuppdi
|
AW: Rückgabe-Typ anpassen?
Nein, CSV und XML sind per se "nur" ein Datenformat und in diesem Format ist keine Implementierung vorgegeben, wie man das im Programm verarbeiten soll.
JSON ist eine Serialisierung von JavaScript-Klassen/Variablen und Delphi kann mit derartigen dynamischen Objektstrukturen nix anfangen. Daß man dieses jetzt für einen systemübergreifenden Datentransfer und somit als "Datenformat" benutzt, ist eine andere Sache. Die meisten Delphi-Implementationen sind "nur" zum Auslesen oder Erstellen, bzw. Serialaisieren/Deserialisieren gedacht, aber zum direkten Verarbeiten sind Viele einfach ungeeignet/umständlich. Genau darum bastel ich mir auch ein JSON-DOM, ähnlich den bekannten XML-DOMs, um das einfacher benutzten und vorallem "editieren" zu können, ohne daß man das JSON erst in ein externes "unbekanntes" Delphi-Objekt deserialisieren muß, bzw. ohne das neu aufbauen/kopieren zu müssen. Fast alle JSON-ReaderImplementationen für Delphi sind quasi read-only oder write-only und es ist, vorallem bei den delphieigenen JSON-Klassen (siehe DBX/DataSnap), recht umständlich aus einem True ein False zu machen. Ist ja so, als wenn man XML als String mit Pos und Copy versucht zu verarbeiten. :stupid: |
AW: Rückgabe-Typ anpassen?
Liste der Anhänge anzeigen (Anzahl: 1)
Habe nun einen kleinen Parser gebastelt der ohne Variant Booleans, Integers und Strings zurückgeben kann.
Leider habe ich keine Ahnung wie ich damit nochmal ein ganzes Objekt oder Variable zurückgeben kann. Ich habe vor die Klasse zum lesen und schreiben benutzen zu können. Ist mein Ansatz brauchbar? Source Code: (Projektdaten auch hochgeladen)
Delphi-Quellcode:
unit JsonObject;
interface uses System.SysUtils, Generics.Collections; type TJsonType = ( jntBoolean, jntInteger, jntString ); TJsonValue = class private FValue : string; function FGetType : TJsonType; public procedure GetBoolean(var AOutput : boolean); procedure GetInteger(var AOutput : integer); procedure GetString(var AOutput : string); procedure Assign(AValue : string); property ValueType : TJsonType read FGetType; end; TJsonNodes = class protected public Values : TDictionary<string, TJsonValue>; constructor Create(); end; TJsonParser = class(TJsonNodes) private protected FJson : string; public constructor Create(); procedure Assign(AJson : string); procedure Parse(AJson : string); overload; procedure Clear(); procedure Parse(); overload; property Json : string read FJson; end; implementation { TJsonParser } procedure TJsonParser.Assign(AJson: string); begin FJson := AJson; end; procedure TJsonParser.Parse(AJson: string); begin FJson := AJson; Parse(); end; procedure TJsonParser.Clear; var CurrentCharIndex: Integer; CurrentChar : char; OutputString : string; InString : boolean; begin InString := False; for CurrentCharIndex := 1 to Length(FJson) do begin CurrentChar := FJson[CurrentCharIndex]; if (CurrentChar = '"') then InString := not InString; if ((CurrentChar = ' ') and (InString = false)) or ((CurrentChar = #10) or (CurrentChar = #13)) then Continue; OutputString := OutputString + CurrentChar; end; FJson := OutputString; end; constructor TJsonParser.Create; begin inherited Create; end; procedure TJsonParser.Parse; var CurrentCharIndex: Integer; CurrentChar : char; LineStarted : boolean; IndexDone : boolean; InIndex : boolean; InValue : boolean; ValueDone : Boolean; InString : boolean; LJsonValue : TJsonValue; StringBuffer : string; LastAdded : string; begin Clear; LineStarted := false; IndexDone := false; InIndex := false; InValue := false; ValueDone := false; InString := False; for CurrentCharIndex := 1 to Length(FJson) do begin CurrentChar := FJson[CurrentCharIndex]; if (CurrentChar = ',') or (CurrentChar = '}') then begin if InString then raise Exception.Create('String should be ended') else if InIndex then raise Exception.Create('Index should be ended') else if IndexDone and not ValueDone then raise Exception.Create('Value should be setted') else begin LineStarted := false; IndexDone := false; InIndex := false; InValue := false; ValueDone := false; InString := False; Values[LastAdded].Assign(StringBuffer); StringBuffer := ''; InValue := false; StringBuffer := ''; end; end; if not LineStarted then begin if CurrentChar = '"' then begin InIndex := true; LineStarted := true; LJsonValue := TJsonValue.Create; Continue; end; end; if LineStarted then begin if (InIndex) and (not InValue) then begin if not(CurrentChar = '"') then begin StringBuffer := StringBuffer + CurrentChar; Continue; end else begin InIndex := false; Values.Add(StringBuffer, LJsonValue); LastAdded := StringBuffer; StringBuffer := ''; Continue; end; end else if (not InIndex) and (not InValue) then begin if CurrentChar = ':' then begin InValue := true; Continue; end; end else if (not InIndex) and (InValue) then begin StringBuffer := StringBuffer + CurrentChar; end; end; end; end; { TJsonValue } procedure TJsonValue.Assign(AValue: string); begin FValue := AValue; end; function TJsonValue.FGetType: TJsonType; begin if (FValue = 'true') or (FValue = 'false') then Result := jntBoolean; end; procedure TJsonValue.GetBoolean(var AOutput: boolean); begin if (FValue = 'true') then AOutput := True else if (FValue = 'false') then AOutput := false else raise Exception.Create(FValue + ' is no Boolean'); end; procedure TJsonValue.GetInteger(var AOutput: integer); begin AOutput := StrToInt(FValue); end; procedure TJsonValue.GetString(var AOutput: string); begin if (FValue[1] = '"') and (FValue[Length(FValue)] = '"') then AOutput := Copy(FValue, 2, Length(FValue)-2); end; { TJsonNodes } constructor TJsonNodes.Create; begin Values := TDictionary<string, TJsonValue>.Create(); end; end.
Delphi-Quellcode:
Json code der so funktioniert:
...
Json : TJsonParser; ... procedure TForm1.btn1Click(Sender: TObject); var LValueID : integer; LValueAge : integer; LValueName : string; LValueURL : string; LValueValid : Boolean; begin Json := TJsonParser.Create; Json.Assign(mmo1.Text); Json.Parse; mmo1.Text := Json.Json; Json.Values['id'].GetInteger(LValueID); Json.Values['age'].GetInteger(LValueAge); Json.Values['name'].GetString(LValueName); Json.Values['url'].GetString(LValueURL); Json.Values['valid'].GetBoolean(LValueValid); Showmessage(IntToStr(LValueID)); Showmessage(IntToStr(LValueAge)); Showmessage(LValueName); Showmessage(LValueURL); if LValueValid then Showmessage('Valid') else Showmessage('Invalid'); end;
Code:
Was haltet ihr von dem ansatz?
{
"id": 1, "age": 18, "name": "Milos", "url": "http://delphipraxis.net", "valid": true } Was würdet ihr anders machen? MfG |
AW: Rückgabe-Typ anpassen?
Dein Zurückgeben geht und man kann es auch so machen,
wobei es natrülich etwas doppelt gemoppelt ist, da der Compiler anhand der Signatur (die Typen der Parameter, aber nicht des Results) auch automatisch unterscheiden kann. (overload)
Delphi-Quellcode:
procedure GetValue(var AOutput : boolean); overload;
procedure GetValue(var AOutput : integer); overload; procedure GetValue(var AOutput : string); overload;
Delphi-Quellcode:
function GetBoolean: boolean;
function GetInteger: integer; function GetString: string; Zitat:
Delphi-Quellcode:
procedure GetValue(AOutput TMyObject); overload;
Hier kann man bei einem besehendem Objekt die Parameter auslesen, man kann auch Parameter zuwisen/ändern und natürlich Methoden aufrufen, aber die Instanz muß extern erstellt und reingegeben werden. Die Instanz wird natürlich auch extern freigegeben, am Besten in der selben Ebene, wie das Erstellen.
Delphi-Quellcode:
procedure GetValue(var AOutput TMyObject); overload;
Wie vorherriges, außer daß man z.B. bei
Delphi-Quellcode:
intern eine neue Instanz erstellen kann.
if noch Assigned(AOutput) then
Von extern kommt also entweder ein Objekt oder nil rein. Und man kann intern das externe Objekt auch freigeben (FreeAndNil) und/oder ein Neues erstellen.
Delphi-Quellcode:
function GetOject: TMyObject;
Hier wird das Objekt ausschließlich intern erstellt. Freigeben muß es dann entweder der Aufrufer und ruft am Ende Free auf. (siehe ![]() Oder der Ersteller ist Owner, verwaltet es intern und gibt es am Ende selbst frei, z.B. wenn er selber freigegeben wird. (siehe Memo.Lines oder Memo.Font). |
AW: Rückgabe-Typ anpassen?
Zitat:
|
AW: Rückgabe-Typ anpassen?
Zitat:
|
AW: Rückgabe-Typ anpassen?
Ob es mit Delphi 7 laufen wird ... mal sehn (die paar RTTI-Funktionen sollten sich abschalten lassen, auch wenn dann die De-Serialisierungsfunktionen fehlen würden)
Die Grundfunktionen meines Codes kommen ohne RTTI aus. (aber fertig ist er noch nicht, siehe Link in 3) ![]() Und der Code des TE wird garaniert auch mit D7 laufen, wenn fertig, denn von RTTI Und Co. ist da drin noch nix zu sehen. Aber ja, es gibt bestimmt auch schon was Fertiges, was in Delphi 7 laufen würde. Wie alt ist JSON eigentlich? Würde auf 2005 oder 2006 tippen und Delphi 7 ist aus dem Jahre 2002, aber wenn ein Code in Delphi 2006/2007 läuft, dann bestimmt auch in D7. |
AW: Rückgabe-Typ anpassen?
Erstmal danke, dass ihr euch Zeit genommen habt um zu antworten.
Zitat:
![]() PasteBin um zu viel Code auf der Seite zu vermeiden. Den Tipp mit GetValue habe ich umgesetzt. Zitat:
Aber wenn das so klappt wie ich es mir denke sollte es kein Problem sein. Was ich nicht ganz verstehe ist, wie ich wieder ein ganzes Objekt zurück geben kann, denn TJsonValue kann TJsonNodes ja gar nicht kennen, wie kann ich dann ein GetValue für TJsonNodes machen der mir dann wieder ein ganzes Objekt zurück gibt? Habe leider nicht ganz verstanden was du mit TMyObject usw gemeint hast. Zitat:
Freundliche Grüsse |
AW: Rückgabe-Typ anpassen?
forward declaration
![]() ![]() Zitat:
Wenn in paar Jahren mal jemand das selbe Problem hat, könnte der externe Code weg sein. |
AW: Rückgabe-Typ anpassen?
Zitat:
Forward Deklarationen waren mir bekannt, jedoch habe ich, das letze mal als ich sie benutzen wollte, wohl einen Fehler gemacht und es hat nicht funktioniert wie ich wollte. Deswegen habe ich es diesmal gar nicht probiert. Zitat:
Der Code sollte in ein paar Tagen hier veröffentlicht werden. :) Momentan ist es noch ein bisschen umständlich:
Delphi-Quellcode:
procedure TForm1.btn1Click(Sender: TObject);
var format: string; child : TJsonObject; int : integer; begin Json := TJsonObject.Create; Json.Parse(mmo1.Text); Json.Format(mmo1.Text, format); mmo1.Text := format; child := TJsonObject.Create; Json['obj'].GetValue(child); child['id'].GetValue(int); showmessage(IntTostr(int)); end;
Code:
Falls Interesse am bisherigen Code steht, einfach nachfragen.
{
"id": 1, "age": 18, "name": "Milos", "url": "http://delphipraxis.net", "valid": true, "obj": { "id": 6, "test": true } } Freundliche Grüsse |
AW: Rückgabe-Typ anpassen?
Liste der Anhänge anzeigen (Anzahl: 2)
Zitat:
Zitat:
Habe gerade Delphi 5 und 7 meines Vaters ausgegraben und die Unit angepasst, damit Sie mit beiden läuft. Das einzige was ich noch testen könnte wäre Delphi 3, habe ich jedoch nach einer Stunde suchen aufgegeben. Delphi 5/7 Projektdateien angehängt. Noch nicht fertig und keine garantie auf stabilität! Edit: Sorry dachte wenn man antwortet wird es automatisch an den letzen Post gefügt. |
AW: Rückgabe-Typ anpassen?
Och, früher sind wir ja auch ohne Generics ausgekommen. :-D
Pssst, du hast Null und das Array vergessen. Array ist wie ein Objekt, nur mit anderen Klammern drumrum und ohne Name vor den Items. Und "neben" Integer gibt es auch noch Float (Double), aber das ist einfach nur ![]() ![]()
Delphi-Quellcode:
.
FormatSettings.DecimalSeparator:='.'
![]() |
AW: Rückgabe-Typ anpassen?
Zitat:
Den Rest weiss ich schon, habe absichtlich betont das es nicht Fertig ist, war die ganze Nacht an einer Homepage und dem Json-Parser dran und wollte eigentlich schlafen gehen. Irgendwie lässt mich aber die (Delphianer-)Praxis nicht gehen :lol: Zitat:
Freundliche Grüsse |
AW: Rückgabe-Typ anpassen?
Liste der Anhänge anzeigen (Anzahl: 1)
Vielen vielen dank an alle helfer, vorallem himitsu.
Der Parser funktioniert nun schon ziemlich gut und ich kann mit ihm auch grössere Blöcke die ich zufällig im Internet rausgesucht habe erfolgreich verabeiten. Ich habe eine kleine GUI geschrieben um die Struktur auszugeben. Wie man sieht wird bei neuen Objekten weiter rein gerückt. Wie soll ich das bei Arrays machen? Bisher hab ich es so gemacht das der Inhalt als string angezeigt wird also: "[item0,item1]" Arrays sind die gelben Felder. Freundliche Grüsse Zitat:
|
AW: Rückgabe-Typ anpassen?
Array = Objekt mit "leeren" Namen der Items.
Bei Objekten "müssen" die Variablen/Property eigentlich immer einen Namen haben und bei Arrays haben die Items keinen Namen. (quasi ein Leerstring) |
AW: Rückgabe-Typ anpassen?
Liste der Anhänge anzeigen (Anzahl: 2)
Danke, habs mit einem Index gelöst. :)
Funktioniert ohne Probleme in Delphi 7 ohne den Code verändern zu müssen ^^ Dieser Code wird auf den Bildern geparst:
Code:
Edit: Gerade nen kleinen Fehler mit Null-Werten entdeckt :)
{
"array": [ 1, 2, 3 ], "Test": 5, "boolean": true, "null": null, "number": 123, "arraytest": [ { "a": true, "c": [ { "test": false, "int": 15 } ] } ], "object": { "a": "b", "c": "d", "e": [ 123, true, "test", 12.3 ] }, "string": "Hello World", "obj": { "firstName": "John", "lastName": "Smith", "isAlive": true, "age": 25, "height_cm": 167.6, "address": { "streetAddress": "21 2nd Street", "city": "New York", "state": "NY", "postalCode": "10021-3100" }, "phoneNumbers": [ { "type": "home", "number": "212 555-1234" }, { "type": "office", "number": "646 555-4567" } ], "children": [], "spouse": null } } |
Alle Zeitangaben in WEZ +1. Es ist jetzt 17:47 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