|
Antwort |
Registriert seit: 8. Jan 2007 472 Beiträge |
#1
Inspiriert durch diese Frage im Forum, wird im siebten Artikel der mORMot Vorstellungsreihe gezeigt, wie mit Hilfe von DocVariant und Virtual TreeView ein Viewer zur Anzeige von Daten im JSON-Format erstellt wird. Im Zusammenspiel zeigen die beiden Komponenten ihre Stärken. Als Datenpool die eierlegende Wollmilchsau DocVariant, hier in Form von TDocVariantData und der TVirtualStringTree als ultraschnelle Anzeige für hierarchische Datenstrukturen. Schnell heißt hier, die Anzeige steht für eine 25MB große JSON-Datei in weniger als 100 Millisekunden. Ich hoffe, die Lust auf Weiterlesen ist geweckt.
Die enthaltenen Verweise zur Dokumentation verlinken zur aktuell verfügbaren mORMot1 Hilfe. Für das Beispiel wird mORMot2 verwendet. Die Namen für Klassen und Funktionen können sich leicht unterscheiden. Im Anhang befindet sich der Sourcecode und das ausführbare Programm. Disclaimer: Der Sourcecode ist weder getestet noch optimiert. Er sollte mit Delphi ab Version 10.3 funktionieren. Die Benutzung der zur Verfügung gestellten Materialien erfolgt auf eigene Gefahr. Die Beispiel-Anwendung ist ein einfacher JSON-Viewer. Das Laden eines Dokuments mit einer Größe von mehreren 100MBs ist möglich. Begrenzt wird es vom vorhandenen Arbeitsspeicher oder der maximalen Länge eines Strings. Mit dem Beispiel Quelltext für das Programm bekommt man:
Um DocVariant zu verstehen, muss man sich mit dem Typ Variant auskennen. Zur Umsetzung werden keine obskuren Hacks, sondern geschickt die vorhandenen Möglichkeiten genutzt. Wem die Klasse System.Variants.TInvokeableVariantType etwas sagt, kann den folgenden Absatz überlesen. Wichtig: Die Ausführungen gelten für 32-Bit und ab Delphi XE7. Für ältere Versionen ist TVariantManager das Stichwort. Die Erklärungen sind nur rudimentär, deshalb begleitend die beiden Einstiegslinks in die Delphi Hilfe zum weiteren Nachlesen: System.Variant, System.Variants.TInvokeableVariantType. 1) Am Anfang der System.Variant Ein Variant wird als 16-Byte großes Record gespeichert. Die ersten beiden Bytes sind für das Typ-Feld (VType) einer Variante reserviert. Für die System-Typen sind Konstanten definiert, z.B. ist es für Boolean die Konstante varBoolean mit dem Wert 11. Der Wertebereich darf die ersten 12 Bits umfassen. Es können eigene Typen registriert werden. Für diese soll der Typ-Wert ab 272 beginnen. Es gibt Funktionen zum Abfragen des Types:
Delphi-Quellcode:
Ein Variant lässt sich nach TVarData hart casten. Das ist möglich, weil gilt: SizeOf(Variant) = SizeOf(TVarData)
und die interne Anordnung nachgebildet wird.
var
v: Variant; begin v := 'Test'; if VarIsType(v, varUString) then ShowMessage(Format('%d - %s', [VarType(v), VarTypeAsText(VarType(v))])); // Result: 258 - UnicodeString
Delphi-Quellcode:
2) DocVariant, ein Variant mit Struktur
var
v: Variant; begin v := True; if TVarData(v).VType = varBoolean then begin var b: Boolean := TVarData(v).VBoolean; ShowMessage(BoolToStr(b, True)); // Result: True Das Geniale an einem DocVariant ist, dass es eine beliebig komplexe Datenstruktur aus Objekt(en) und/oder Arrays, oder aus Kombinationen von beiden sein kann. Nur der zur Verfügung stehende Arbeitsspeicher ist die Grenze. In der mORMot Hilfe steht: "A custom variant type used to store any JSON/BSON document-based content - i.e. name/value pairs for objects, or an array of values (including nested documents), stored in a TDocVariantData memory structure". Die DocVariant Syntax sieht für Pascal Entwickler etwas gewöhnungsbedürftig aus, weil es eher an eine Scriptsprache erinnert. Mehr dazu in der Hilfe nachlesen. Ein DocVariant lässt sich auf mehrere Arten erstellen. Ein Beispiel zum Kennenlernen:
Delphi-Quellcode:
Es wird ein DocVariant vom Typ Objekt mit den Variant-Optionen [dvoReturnNullForUnknownProperty, dvoValueCopiedByReference] erzeugt. Beim Erstellen kann der Typ und/oder die Variant-Option(en) angeben, oder eine der neun New... Initialisierungsfunktionen mit sinnvoller Vorauswahl verwendet werden. Was man auch im Beispiel sieht, ist die Möglichkeit Pseudo-Eigenschaften oder -Funktionen aufzurufen. Von diesen gibt es eine stattliche Anzahl: _Count, _Kind, _JSON, _(idx), Value(idx), Value(Name), Name(idx), Add(Item), Add(Name, Value), Exists(Name), Delete(idx), Delete(Name) und NameIndex(Name).
var
v: Variant; begin TDocVariant.NewFast(v); v.a := 10; v.b := 3.3; v.c := True; // DocVariant: Count=3, Value(0)=10, Value('b')=3,30, NameIndex('b')=2 ShowMessage(Format('DocVariant: Count=%d, Value(0)=%d, Value(''b'')=%f, NameIndex(''b'')=%d', [Integer(v._Count), Integer(v.Value(0)), Double(v.Value('b')), Integer(v.NameIndex('b'))])); 3) Ein Variant und seine Pseudo-Eigenschaften und -Funktionen Wie oben beschrieben, besitzt jeder Variant einen Typ-Wert. Dieser Wert ist gleichzeitig Selektor, der bestimmt, welche Bearbeitungsklasse herangezogen wird. Für selbst definierte Variant-Typen, zum einfachen und schnellen Zugriff auf alle Erweiterungen, verwendet mORMot die eigene Basisklasse TSynInvokeableVariantType, abgeleitet von TInvokeableVariantType. Diese Klasse ist Vorfahre der Klassen: TDocVariant, TBsonVariant, TSqlDBRowVariantType und TOrmTableRowVariant. Mit der Funktion SynRegisterCustomVariantType werden diese Klassen im System registriert. Eine Objektinstanz der Klasse TDocVariant wird nach der Registrierung in der Variable DocVariantType gespeichert. Wichtiger in der Praxis ist die Variable DocVariantVType. Sie enthält den VarType Wert, mit dem ein DocVariant im System registriert ist. Immer wenn jetzt ein Variant mit dem Typ-Wert DocVariantVType unterwegs ist, steht im Hintergrund die Klasse TDocVariant zur Übernahme der eigentlichen Arbeit parat. Am einfachsten lässt es sich nachvollziehen, wenn in der Unit mormot.core.variants Breakpoints an den Anfang der Funktionen TSynInvokeableVariantType.DispInvoke, TDocVariant.DoFunction und TDocVariant.DoProcedure gesetzt werden. Schritt für Schritt lässt sich dann nachverfolgt, wie die Abläufe im Hintergrund vonstattengehen. 4) TDocVariantData, das Geheimnis hinter DocVariant Kein Geheimnis, aber eine clevere Idee. Die Struktur von TDocVariantData ist wie folgt definiert:
Delphi-Quellcode:
Wer richtig zählt, kommt auf 16-Bytes. Damit gilt: SizeOf(Variant) = SizeOf(TVarData) = SizeOf(TDocVariantData)
. Wer schreibt TDocVariant.New(v);
ruft folgende Funktion auf:
TDocVariantData = record
private // note: this structure uses all TVarData available space: no filler needed! VType: TVarType; // 16-bit VOptions: TDocVariantOptions; // 16-bit VName: TRawUtf8DynArray; // pointer VValue: TVariantDynArray; // pointer VCount: integer; // 32-bit
Delphi-Quellcode:
Es initialisiert sich eine Struktur zur Aufnahme von Name-Value Pairs. Es stehen über 150 Eigenschaften und Funktionen, davon mehr als 30 unterschiedliche Init-Varianten, zur Verfügung.
procedure TDocVariantData.Init(aOptions: TDocVariantOptions);
begin VType := DocVariantVType; aOptions := aOptions - [dvoIsArray, dvoIsObject]; VOptions := aOptions; pointer(VName) := nil; // to avoid GPF when mapped within a TVarData/variant pointer(VValue) := nil; VCount := 0; end; 5) Immer auf der sicheren Seite Man kann einen Variant nach TDocVariantData hart casten. Besser ist es, dafür die Funktion _Safe zu verwenden. Diese Funktion sorgt dafür, dass immer ein DocVariant zurückgeliefert wird. Wenn kein realer vorhanden ist, dann ein Fake-Variant. Dieser ist in der Konstante DocVariantDataFake definiert. Damit lässt es sich schön spuken. Jeder Strich ist wichtig:
Delphi-Quellcode:
Wie schnell einzelne Bibliotheken beim Einlesen einer JSON-Datei mit 1MB Größe sind, hierzu ein paar Benchmark-Werte:
var
v: Variant; begin v := _JSON('{"First": "Tom", "Last": "Selleck", "Profession": "Actor"}'); // First: Tom, Last: Selleck, Profession: Actor ShowMessage(Format('First: %s, Last: %s, Profession: %s', [v.First, v.Last, v.Profession])); // What happens if you write the following? // Example 1: var isPeaPuree: Boolean := TDocVariantData(v).O['One'].O['Two'].O['Three'].B['IsPeaPuree']; ShowMessage(Format('Does he like pea puree? %s', [BoolToStr(isPeaPuree, True)])); // Result: "Does he like pea puree? False" // Example 2: _Safe(v).O_['One'].O_['Two'].O_['Three'].B['IsPeaPuree'] := True; ShowMessage(v._JSON); // Result: "{"First":"Tom","Last":"Selleck","Profession":"Actor","One":{"Two":{"Three":{"IsPeaPuree":true}}}}"
JSON-Viewer Eines vorweg: Das Design des Viewers ist hübsch hässlich. Da noch einige nette Funktionen auf meiner Festplatte liegen, wird das Styling auf Version 2, wahrscheinlicher in die Ewigkeit verschoben. Mit dem erworbenen Vorwissen ausgestattet, jetzt zur konkreten Implementierung in der Anwendung. Der Viewer ist ausgelagert und von einem Panel abgeleitet. Damit ist die Anzeige von der Logik getrennt und lässt sich wie eine Komponente handhaben. Sie besteht nur aus wenigen Funktionen. Für den Durchgriff auf die Daten sind Methodenreferenzen definiert. Ihr Aufbau ist wie folgt:
Delphi-Quellcode:
Zur Abbildung des Dokuments sind nur wenige Zeilen Quelltext notwendig:
TJsonTreePresenter = class(TCustomPanel)
strict private FTree: TVirtualStringTree; FDocData: PDocVariantData; FDocDataName: String; private ... procedure DoTreeInitNode(pmSender: TBaseVirtualTree; pmParentNode, pmNode: PVirtualNode; var pmvInitialStates: TVirtualNodeInitStates); procedure DoTreeInitChildren(pmSender: TBaseVirtualTree; pmNode: PVirtualNode; var pmvChildCount: Cardinal);
Delphi-Quellcode:
Für den Zugriff auf den Datenbaum sind die Funktionen EditSelectedDocData, DeleteSelectedDocData und ProcessSelectedDocNode vorhanden. Ich hoffe, ich habe am Anfang nicht zu viel versprochen und das Weiterlesen hat sich gelohnt. Wer keine ausreichend große JSON-Datei zum Testen zur Verfügung hat, kann sich Daten von GitHub laden.
procedure TJsonTreePresenter.DoTreeInitNode(pmSender: TBaseVirtualTree; pmParentNode, pmNode: PVirtualNode; var pmvInitialStates: TVirtualNodeInitStates);
var title: String; docVD: PDocVariantData; begin if pmParentNode = Nil then begin title := FDocDataName; docVD := FDocData; end else begin docVD := PJsonDocNode(pmParentNode.GetData).docVData; if docVD <> Nil then begin title := DocVariantPairCaption(docVD, pmNode.Index); docVD := _Safe(docVD.Values[pmNode.Index]); end; end; if (docVD <> Nil) and (docVD.VarType = DocVariantVType) then begin var nodeData: PJsonDocNode := PJsonDocNode(pmNode.GetData); nodeData.nodeName := title; nodeData.docVData := docVD; if docVD.Count > 0 then Include(pmvInitialStates, ivsHasChildren); end; end; procedure TJsonTreePresenter.DoTreeInitChildren(pmSender: TBaseVirtualTree; pmNode: PVirtualNode; var pmvChildCount: Cardinal); var docVD: PDocVariantData; begin docVD := PJsonDocNode(pmNode.GetData).docVData; if docVD <> Nil then pmvChildCount := docVD.Count; end; Zusammenfassung mORMot ist gut dokumentiert. Die Hilfe umfasst mehr als 2500 Seiten. Davon enthalten die ersten ca. 650 Seiten einen sehr lesenswerten allgemeinen Teil, der Rest ist API Dokumentation. mORMot muss nicht in der IDE installierten werden! Es reicht aus, die Bibliothek mit den Static-Dateien in ein Verzeichnis zu kopieren und die entsprechenden Bibliothekspfade anzulegen. In 3 Minuten ist es auf dem Rechner und bei Nichtgefallen in 3 Sekunden wieder spurlos entfernt. Es stehen viele Beispiele und ein freundliches Forum zur Verfügung. In der mORMot Reihe bisher veröffentlicht:
Thomas |
|||||||||||||||||||||||||||||||||||||||||||||
Zitat |
Themen-Optionen | Thema durchsuchen |
Ansicht | |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
LinkBack |
LinkBack URL |
About LinkBacks |