Sign in Join
Wiki copytemporary

LGenerics: what changed

Revision 3 to 4. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## About
22
33Collection of generic algorithms and data structures for Free Pascal.
44
55Requires: FPC 3.2.2+, Lazarus 2.2+.
66
77Author: A.Koverdyaev (avk)
88
99License: Apache License 2.0
1010
1111## Download
1212
1313GitHub: <https://github.com/avk959/LGenerics>
1414
1515## Features
1616
1717#### Implemented primitives
1818
1919- stack (unit lgStack)
2020- queue (unit lgQueue)
2121- deque (unit lgDeque)
2222- vector (unit lgVector)
2323- vector of bits (unit lgVector)
2424- priority queue based on binary heap (unit lgPriorityQueue)
2525- priority queue with key update and melding based on pairing heap (unit lgPriorityQueue)
2626- sorted list (unit lgList)
2727- hashed list - array based list with the ability to fast search by key (unit lgList)
2828- hashset (unit lgHashSet)
2929- fine-grained concurrent hashset (unit lgHashSet)
3030- sorted set (unit lgTreeSet)
3131- set of arbitrary size (unit lgUtil, TGSet)
3232- hash multiset (unit lgHashMultiSet)
3333- fine-grained concurrent hashmultiset (unit lgHashMultiSet)
3434- sorted multiset (unit lgTreeMultiSet)
3535- hashmap (unit lgHashMap)
3636- fine-grained concurrent hashmap (unit lgHashMap)
3737- sorted map (unit lgTreeMap)
3838- hash multimap (unit lgMultiMap)
3939- tree multimap (unit lgMultiMap)
4040- list miltimap (unit lgMultiMap)
4141- bijective map (unit lgBiMap)
4242- sparse 2D table (unit lgTable2D)
4343- disjoint set (unit lgHashSet)
4444- AVL tree (unit lgAvlTree)
4545- red-black tree (unit lgRbTree)
4646- some treap variants (unit lgTreap)
4747- general rooted tree (unit lgRootTree)
4848- sparse labeled undirected graph (unit lgSimpleGraph)
4949- sparse labeled directed graph (unit lgSimpleDigraph)
5050- lite containers based on advanced records
5151- extended IEnumearble interface - filtering, mapping, etc.
5252
5353#### Algorithms on graphs
5454
5555- core functions:
5656 - vertices/edges addition/removal/query/enumeration, edge contraction, degree
5757 - load/save to own binary format, primitive export to DOT format
5858- connectivity:
5959 - connected/strongly connected components, bipartite detection, degeneracy, k-core
6060 - articulation points, bridges, biconnected components
6161 - edge-connectivity
6262- traversals:
6363 - BFS/DFS traversals with visitors,
6464 - cycle/negative cycle detection,
6565 - topological sort
6666- operations:
6767 - induced subgraphs, complement, reverse, union, intersect, symmetric difference,
6868- chordality testing
6969- planarity testing: FMR Left-Right Planarity algorithm
7070- distance within graph:
7171 - eccentricity, radius, diameter, center, periphery
7272- matching:
7373 - maximum cardinality matching on bipartite/arbitrary graphs
7474 - minimum/maximum weight matching on bipartite graphs
7575- dominators in flowgraps: simple iterative and Semi-NCA algorithms
7676- some suggestions for NP-hard problems:
7777 - maximum independent set, maximal independent sets enumeration
7878 - maximum clique, cliques enumeration
7979 - minimum vertex cover, minimal vertex covers enumeration
8080 - vertex coloring, approximations and exact
8181 - minimum dominating set
8282 - Hamiltonian cycles and paths
8383 - local search TSP approximations, BnB TSP solver
8484- minimum spanning trees: Prims's and Kruskal's algorithms
8585- single source shortest paths:
8686 - Dijkstra with pairing heap, Bellman-Ford-Moor with Tarjan's subtree disassembly(BFMT)
8787- single pair shortest paths:
8888 - Dijkstra with binary heap, BFMT, bidirection Dijkstra, A*, NBA*
8989- all pairs shortest paths:
9090 - Floyd–Warshall, Johnson, BFMT
9191- networks:
9292 - maximum flow: push/relabel, capacity scaling Dinitz
9393 - minimum-cost flow: Busacker-Gowen, cost scaling push/relabel algorithm
9494 - global minimum cut: Stoer–Wagner, Nagamochi-Ibaraki
9595
9696#### Algorithms on arrays and vectors
9797
9898(mostly unit lgArrayHelpers)
9999
100100- reverse, right/left cyclic shifts
101101- permutations
102102- binary search
103103- N-th order statistics
104104- inversion counting
105105- distinct values selection
106106- quicksort
107107- introsort
108108- dual pivot quicksort
109109- mergesort
110110- timsort (unit lgMiscUtils)
111111- counting sort
112112- radix sort
113113- translation of Orson Peters' PDQSort algorithm
114114- static segment tree
115115- longest increasing subsequence
116116- ...
117117
118118#### Algorithms on strings and sequences
119119
120120(units lgStrHelpers, lgSeqUtils)
121121
122122- exact string matching
123123 - Boyer-Moore string matching algorithm(in Fast Search variant, unit lgStrHelpers)
124124 - Boyer-Moore-Horspool-Raita algorithm (unit lgStrHelpers)
125125 - Aho-Corasick automation for multiple string matching
126126- longest common subsequence of two sequences:
127127 - reducing the LCS problem to LIS
128128 - Kumar-Rangan algorithm for LCS
129129 - Myers algorithm for LCS
130130- the Levenshtein distance:
131131 - simple DP algorithm
132132 - modified Berghel-Roach algorithm
133133 - Myers bit-vector algorithm with cut-off heuristic
134134- LCS distance:
135135 - Myers algorithm for LCS distance
136136- fuzzy string matching(k differences)
137137 - Ukkonen EDP algorithm
138138- fuzzy string matching with preprocessing(something similar to fuzzywuzzy)
139139
140140#### Other features
141141
142142- non-cryptogarphic hashes (unit lgHash):
143143 - Yann Collet's xxHash32, xxHash64
144144 - Austin Appleby's MurmurHash2, MurmurHash2A, MurmurHash3_x86_32, MurmurHash64A
145145- brief and dirty implementation of futures concept (unit lgAsync)
146146- brief channel implementation (unit lgAsync)
147147- brief implementation of thread pool (unit lgAsync)
148148- 128-bit integers (unit lgInt128)
149149- JSON validator/parser/generator (unit lgJson)
150150- JSON Patch/Diff (unit lgJson)
151151- JSON serialization/deserialization of native Pascal data structures using RTTI(unit lgPdo)
152152- JSON Type Definition schemas(unit lgJsonTypeDef)
153153 - validating a JSON document against a JTD schema(unit lgJsonTypeDef)
154154 - generating Pascal code from a JTD schema(unit lgJtdCodegen)
155155 - inferring JTD schemas from example data(unit lgJtdInfer)
156156- JSONPath implementation(unit LgJsonPath)
157157- CSV document processing (unit lgCsvUtils)
158158- Eisel-Lemire fast string-to-double conversion algorithm (unit lgJson)
159159- Ryū double-to-string conversion algorithm (unit lgJson)
160160
161161## Support for JSON Patch
162162
163163The TJsonPatch class from the lgJson unit implements the JSON Patch ([RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902)) functionality. JSON Patch is a JSON document format (media type is application/json-patch+json) for expressing a sequence of operations applied to a target JSON document; it is always an array of objects, each representing a single operation, supported set of operations: ADD, REMOVE, REPLACE, MOVE, COPY, and TEST.
164164
165165For example, for this document:
166166
167167``` json
168168 {
169169 "foo": [1, 3],
170170 "boo": ["bar", "baz"]
171171 }
172172```
173173
174174applying this patch
175175
176176``` json
177177 [
178178 {"op": "add", "path": "/foo/1", "value": "fizz"},
179179 {"op": "replace", "path": "/boo/1", "value": "qux"},
180180 {"op": "add", "path": "/ozz", "value": "zoo"}
181181 ]
182182```
183183
184184will have the following result:
185185
186186``` json
187187 {
188188 "foo": [1, "fizz", 3],
189189 "boo": ["bar", "qux"],
190190 "ozz": "zoo"
191191 }
192192```
193193
194194The TJsonPatch class also contains a POC implementation of the Diff utility, which generates a patch by comparing source and target documents.
195195
196196For example, for this source:
197197
198198``` json
199199 {
200200 "a": 1,
201201 "b": null,
202202 "c": ["test", "plop"]
203203 }
204204```
205205
206206and for this target:
207207
208208``` json
209209 {
210210 "a": 6,
211211 "c": ["test", "flop"],
212212 "d": "baz"
213213 }
214214```
215215
216216it will generate a patch like this:
217217
218218``` json
219219 [
220220 {"op": "replace", "path": "/a", "value": 6},
221221 {"op": "remove", "path": "/b"},
222222 {"op": "replace", "path": "/c/1", "value": "flop"},
223223 {"op": "add", "path": "/d", "value": "baz"}
224224 ]
225225```
226226
227227## Exporting structures to JSON format
228228
229229A unit has been added to LGenerics for exporting Pascal data structures to JSON format and vice versa. The following types are currently supported (called PDOs for short):
230230
231231- numeric, boolean or string types, some limited support of variants;
232232- enumerations (exported in string form as the name of the corresponding constant);
233233- sets (exported as an array of its elements);
234234- regular records, it is possible to register a list of field names or a custom serialization callback;
235235- classes, using published properties or by registering a custom callback (TStrings and TCollection as a special case);
236236- objects (only by registering a custom serialization callback);
237237- static arrays of PDO (only one-dimensional, multidimensional arrays are written as one-dimensional);
238238- dynamic arrays of PDO;
239239- variant arrays (currently only one-dimensional);
240240
241241### Serialization
242242
243243Function PdoToJson() saves native data structure to JSON format.
244244
245245For example, this code:
246246
247247``` pascal
248248program test;
249249
250250{$mode objfpc}{$h+}{$optimization noorderfields}
251251
252252uses
253253 SysUtils, lgUtils, lgJson, lgPdo;
254254
255255type
256256 TDayOfWeek = 1..7;
257257 TDaySet = set of TDayOfWeek;
258258 TMember = record
259259 Name: string;
260260 Age: Integer;
261261 VisitDays: TDaySet;
262262 end;
263263
264264const
265265 Members: array of TMember = (
266266 (Name: 'Alice'; Age: 21; VisitDays: [1, 3, 5]),
267267 (Name: 'Bob'; Age: 25; VisitDays: [2, 5, 6]));
268268var
269269 n: specialize TGAutoRef<TJsonNode>;
270270 s: string;
271271
272272begin
273273 RegisterRecordFields(TypeInfo(TMember), ['name', 'age', 'visitDays']);
274274 s := PdoToJson(TypeInfo(Members), Members);
275275 if not n.Instance.Parse(s) then
276276 WriteLn('Oops, something went wrong :(')
277277 else
278278 WriteLn(n.Instance.FormatJson([jfoEgyptBrace, jfoSingleLineArray]));
279279end.
280280```
281281
282282produces this JSON:
283283
284284``` json
285285[
286286 {
287287 "name": "Alice",
288288 "age": 21,
289289 "visitDays": [1, 3, 5]
290290 },
291291 {
292292 "name": "Bob",
293293 "age": 25,
294294 "visitDays": [2, 5, 6]
295295 }
296296]
297297```
298298
299299### Deserialization
300300
301301Procedure PdoLoadJson() is used to load JSON back to the native data structure.
302302
303303Let's change the previous example a bit:
304304
305305``` pascal
306306program test;
307307
308308{$mode objfpc}{$h+}{$optimization noorderfields}
309309
310310uses
311311 SysUtils, lgJson, lgPdo, lgMiscUtils;
312312
313313type
314314 TDayOfWeek = 1..7;
315315 TDaySet = set of TDayOfWeek;
316316 TMember = record
317317 Name: string;
318318 Age: Integer;
319319 VisitDays: TDaySet;
320320 end;
321321
322322const
323323 Members: array of TMember = (
324324 (Name: 'Alice'; Age: 21; VisitDays: [1, 3, 5]),
325325 (Name: 'Bob'; Age: 25; VisitDays: [2, 5, 6]));
326326var
327327 s: string;
328328 MemberList: array of TMember = nil;
329329 I: Integer;
330330begin
331331 RegisterRecordFields(TypeInfo(TMember), ['name', 'age', 'visitDays']);
332332 s := PdoToJson(TypeInfo(Members), Members);
333333 //Let's try to read the resulting JSON back into the MemberList array
334334 PdoLoadJson(TypeInfo(MemberList), MemberList, s);
335335 for I := 0 to High(MemberList) do begin
336336 WriteLn('Member ', I);
337337 WriteLn(' Name: ', MemberList[I].Name);
338338 WriteLn(' Age: ', MemberList[I].Age);
339339 WriteLn(' VisitDays: ', Stringify(TypeInfo(TDaySet), MemberList[I].VisitDays));
340340 end;
341341end.
342342```
343343
344344it prints
345345
346346 Member 0
347347 Name: Alice
348348 Age: 21
349349 VisitDays: {1, 3, 5}
350350 Member 1
351351 Name: Bob
352352 Age: 25
353353 VisitDays: {2, 5, 6}
354354
355355## Support for JSON Type Definition
356356
357357[JSON Type Definition (aka RFC 8927)](https://jsontypedef.com) is an easy-to-learn, standardized way to define a schema for JSON data.
358358
359359##### Why a schema for JSON?
360360
361361The schema provides a way to describe the structure and to some extent the semantics of the document, with which:
362362
363363- it becomes possible to validate the document, i.e. to distinguish the "correct" document from the "incorrect" one;
364364- a validated document can be safely deserialized into a native data structure;
365365- and finally, it becomes possible to automatically generate this particular native data structure;
366366
367367##### Why JSON Typedef?
368368
369369It is simple, standardized, well documented, and there are a fair number of implementations for various programming languages(of course, Pascal is not among them).
370370
371371Strictly speaking, JSON Typedef is a subset of JSON Schema. According to the author, the expressiveness of the JTD was intentionally limited to be no more powerful than what can be expressed in the type systems of of major programming languages.
372372
373373In short, for most projects this may be exactly what you need.
374374
375375TL;DR
376376
377377There is not much point in describing the features of the JTD schemas, it is well explained in the documentation.
378378
379379##### Validation
380380
381381Suppose we should to receive JSON messages like
382382
383383``` json
384384{
385385 "firstName": "John",
386386 "lastName": "Doe",
387387 "age": 42
388388}
389389```
390390
391391such JSON can be described by the JTD scheme  
392392
393393``` json
394394{
395395 "properties": {
396396 "firstName": {"type": "string"},
397397 "lastName": {"type": "string"},
398398 "age": {"type": "uint8"}
399399 }
400400}
401401```
402402
403403And let us get the message
404404
405405``` json
406406{
407407 "firstName": "Bill",
408408 "lastName": "Smith",
409409 "age": -5
410410}
411411```
412412
413413which we need to check for consistency with the scheme.
414414
415415First we need to load the JSON representation of the schema into the corresponding Pascal structure - TJtdSchema class. For example, let our schema be located in the file PersonSchema.json, code
416416
417417``` pascal
418418 Schema := TJtdSchema.Create;
419419 try
420420 Schema.LoadFromFile('PersonSchema.json')
421421 except
422422 //something to do in case of an exception
423423 end;
424424```
425425
426426will load the schema and then it can be used repeatedly. In turn, the received JSON(let it be the string S) should be loaded into TJsonNode class, for example like this:
427427
428428``` pascal
429429 Person := TJsonNode.Create;
430430 Person.AsJson := S;
431431```
432432
433433The Validate(yep!) function is used to validate against the schema; its main parameters are the loaded JSON sample, the loaded schema, and the (out) list of errors found. The error list is an array of InstancePath/SchemaPath pairs, which are JSON pointers. The first one points to the part of the document(instance) that is invalid, and the second one points to the part of the schema that rejected that input. The purpose of the other parameters is described in detail in the lgJsonTypeDef source.
434434
435435So this code
436436
437437``` pascal
438438var
439439 ErrList: TJtdErrorList;
440440 e: TValidateError;
441441 ...
442442 case Validate(Person, Schema, ErrList) of
443443 jvrOk: {It's okay, the document is correct};
444444 jvrErrors:
445445 for e in ErrList do
446446 WriteLn('Error: instance path = ', e.InstancePath, ', schema path = ', e.SchemaPath);
447447 else
448448 //dealing with the remaining cases
449449 end;
450450```
451451
452452will print
453453
454454
455455 Error: instance path = /age, schema path = /properties/age/type
456456
457457##### Code generation from schemas
458458
459459Because of RTTI's limited capabilities, it was decided to create a separate hierarchy of classes(see lgJtdTypes unit) to generate the Pascal code according to the given JTD scheme.
460460
461461This hierarchy would have to reflect the JSON Typedef type system and be able to load/save itself from/to different JSON representations.
462462
463463The part of the implementation responsible for code generation, specifically the TJtdPasCodegen class, can be found in the lgJtdCodegen unit.
464464
465465To generate a Pascal unit, the TJtdPasCodegen class needs the schema itself (mandatory), the name of the unit (desirable), the name of the root class (optional), and if the code is to be saved to a file, the name of the file.
466466
467467For example, suppose we have the following scheme:
468468
469469``` json
470470{
471471 "definitions": {
472472 "fruits": {"enum": ["apple", "banana", "strawberry"],
473473 "metadata": {
474474 "description": "this is fruits enumeration description",
475475 "enumDescription": {
476476 "apple": "this is apple description",
477477 "banana": "this is banana description",
478478 "strawberry": "this is strawberry description"
479479 }
480480 }
481481 },
482482 "nameList": {"elements": {"type": "string"}}
483483 },
484484 "properties": {
485485 "firstName": {"type": "string"},
486486 "lastName": {"type": "string"},
487487 "age": {"type": "uint8"},
488488 "friends": {"ref": "nameList", "nullable": true},
489489 "favoriteFruit": {"ref": "fruits", "nullable": true}
490490 },
491491 "metadata": {
492492 "description": "this is Person description",
493493 "preferredName": "person"
494494 }
495495}
496496```
497497
498498and we have loaded it into a TJtdSchema instance named PersonSchema, code
499499
500500``` pascal
501501 with TJtdPasCodegen.Create(PersonSchema) do
502502 try
503503 UnitName := 'Person';
504504 SaveToFile('person.pas');
505505 finally
506506 Free;
507507 end;
508508```
509509
510510will create a (nice-looking :)) unit in the application folder, whose root class will be able to load/save JSONs compliant to the schema.
511511
512512``` pascal
513513{
514514 This unit was automatically created by JtdPasCodegen, do not edit.
515515}
516516unit Person;
517517
518518{$MODE OBJFPC}{$H+}{$B-}
519519
520520interface
521521
522522uses
523523 SysUtils, lgJson, lgJtdTypes;
524524
525525type
526526
527527 TNameList = class sealed(specialize TJtdList<TJtdString>)
528528 class function GetJtdClass: TJtdEntityClass; override;
529529 end;
530530
531531{ this is fruits enumeration description }
532532 TFruits = (
533533 apple, // this is apple description
534534 banana, // this is banana description
535535 strawberry // this is strawberry description
536536 );
537537
538538{ Container for some TFruits enumeration element }
539539 TFruitsElem = class sealed(specialize TJtdEnum<TFruits>)
540540 class function GetJtdClass: TJtdEntityClass; override;
541541 end;
542542
543543{ this is Person description }
544544 TPerson = class sealed(TJtdObject)
545545 private
546546 FFirstName: TJtdString;
547547 FLastName: TJtdString;
548548 FAge: TJtdUInt8;
549549 FFriends: TNameList;
550550 FFavoriteFruit: TFruitsElem;
551551 procedure SetFirstName(aValue: TJtdString);
552552 procedure SetLastName(aValue: TJtdString);
553553 procedure SetAge(aValue: TJtdUInt8);
554554 procedure SetFriends(aValue: TNameList);
555555 procedure SetFavoriteFruit(aValue: TFruitsElem);
556556 protected
557557 procedure DoReadJson(aNode: TJsonNode); override;
558558 procedure DoReadJson(aReader: TJsonReader); override;
559559 procedure DoWriteJson(aWriter: TJsonStrWriter); override;
560560 public
561561 class function GetJtdClass: TJtdEntityClass; override;
562562 procedure Clear; override;
563563 { refers to "firstName" JSON property }
564564 property FirstName: TJtdString read FFirstName write SetFirstName;
565565 { refers to "lastName" JSON property }
566566 property LastName: TJtdString read FLastName write SetLastName;
567567 { refers to "age" JSON property }
568568 property Age: TJtdUInt8 read FAge write SetAge;
569569 { refers to "friends" JSON property; is nullable }
570570 property Friends: TNameList read FFriends write SetFriends;
571571 { refers to "favoriteFruit" JSON property; is nullable }
572572 property FavoriteFruit: TFruitsElem read FFavoriteFruit write SetFavoriteFruit;
573573 end;
574574
575575implementation
576576
577577{ TNameList }
578578
579579class function TNameList.GetJtdClass: TJtdEntityClass;
580580begin
581581 Result := TNameList;
582582end;
583583
584584{ TFruitsElem }
585585
586586class function TFruitsElem.GetJtdClass: TJtdEntityClass;
587587begin
588588 Result := TFruitsElem;
589589end;
590590
591591{ TPerson }
592592
593593class function TPerson.GetJtdClass: TJtdEntityClass;
594594begin
595595 Result := TPerson;
596596end;
597597
598598procedure TPerson.Clear;
599599begin
600600 FreeAndNil(FFirstName);
601601 FreeAndNil(FLastName);
602602 FreeAndNil(FAge);
603603 FreeAndNil(FFriends);
604604 FreeAndNil(FFavoriteFruit);
605605end;
606606
607607procedure TPerson.SetFirstName(aValue: TJtdString);
608608begin
609609 if aValue = FFirstName then exit;
610610 FFirstName.Free;
611611 FFirstName := aValue;
612612end;
613613
614614procedure TPerson.SetLastName(aValue: TJtdString);
615615begin
616616 if aValue = FLastName then exit;
617617 FLastName.Free;
618618 FLastName := aValue;
619619end;
620620
621621procedure TPerson.SetAge(aValue: TJtdUInt8);
622622begin
623623 if aValue = FAge then exit;
624624 FAge.Free;
625625 FAge := aValue;
626626end;
627627
628628procedure TPerson.SetFriends(aValue: TNameList);
629629begin
630630 if aValue = FFriends then exit;
631631 FFriends.Free;
632632 FFriends := aValue;
633633end;
634634
635635procedure TPerson.SetFavoriteFruit(aValue: TFruitsElem);
636636begin
637637 if aValue = FFavoriteFruit then exit;
638638 FFavoriteFruit.Free;
639639 FFavoriteFruit := aValue;
640640end;
641641
642642{$PUSH}{$WARN 5057 OFF}
643643procedure TPerson.DoReadJson(aNode: TJsonNode);
644644var
645645 p: TJsonNode.TPair;
646646 Flags: array[0..4] of Boolean;
647647 I: Integer;
648648begin
649649 if not aNode.IsObject then ReadError;
650650 Clear;
651651 System.FillChar(Flags, SizeOf(Flags), 0);
652652 for p in aNode.Entries do
653653 case p.Key of
654654 'firstName':
655655 begin
656656 FFirstName := TJtdString(TJtdString.ReadJson(p.Value));
657657 Flags[0] := True;
658658 end;
659659 'lastName':
660660 begin
661661 FLastName := TJtdString(TJtdString.ReadJson(p.Value));
662662 Flags[1] := True;
663663 end;
664664 'age':
665665 begin
666666 FAge := TJtdUInt8(TJtdUInt8.ReadJson(p.Value));
667667 Flags[2] := True;
668668 end;
669669 'friends':
670670 begin
671671 FFriends := TNameList(TNameList.ReadJson(p.Value));
672672 Flags[3] := True;
673673 end;
674674 'favoriteFruit':
675675 begin
676676 FFavoriteFruit := TFruitsElem(TFruitsElem.ReadJson(p.Value));
677677 Flags[4] := True;
678678 end;
679679 else
680680 UnknownProp(p.Key);
681681 end;
682682 for I := 0 to System.High(Flags) do
683683 if not Flags[I] then
684684 case I of
685685 0: PropNotFound('firstName');
686686 1: PropNotFound('lastName');
687687 2: PropNotFound('age');
688688 3: PropNotFound('friends');
689689 4: PropNotFound('favoriteFruit');
690690 else
691691 end;
692692end;
693693{$POP}
694694
695695{$PUSH}{$WARN 5057 OFF}
696696procedure TPerson.DoReadJson(aReader: TJsonReader);
697697var
698698 Flags: array[0..4] of Boolean;
699699 I: Integer;
700700begin
701701 if aReader.TokenKind <> tkObjectBegin then ReadError;
702702 Clear;
703703 System.FillChar(Flags, SizeOf(Flags), 0);
704704 repeat
705705 if not aReader.Read then ReadError;
706706 if aReader.TokenKind = tkObjectEnd then break;
707707 case aReader.Name of
708708 'firstName':
709709 begin
710710 FFirstName := TJtdString(TJtdString.ReadJson(aReader));
711711 Flags[0] := True;
712712 end;
713713 'lastName':
714714 begin
715715 FLastName := TJtdString(TJtdString.ReadJson(aReader));
716716 Flags[1] := True;
717717 end;
718718 'age':
719719 begin
720720 FAge := TJtdUInt8(TJtdUInt8.ReadJson(aReader));
721721 Flags[2] := True;
722722 end;
723723 'friends':
724724 begin
725725 FFriends := TNameList(TNameList.ReadJson(aReader));
726726 Flags[3] := True;
727727 end;
728728 'favoriteFruit':
729729 begin
730730 FFavoriteFruit := TFruitsElem(TFruitsElem.ReadJson(aReader));
731731 Flags[4] := True;
732732 end;
733733 else
734734 UnknownProp(aReader.Name);
735735 end;
736736 until False;
737737 for I := 0 to System.High(Flags) do
738738 if not Flags[I] then
739739 case I of
740740 0: PropNotFound('firstName');
741741 1: PropNotFound('lastName');
742742 2: PropNotFound('age');
743743 3: PropNotFound('friends');
744744 4: PropNotFound('favoriteFruit');
745745 else
746746 end;
747747end;
748748{$POP}
749749
750750procedure TPerson.DoWriteJson(aWriter: TJsonStrWriter);
751751begin
752752 aWriter.BeginObject;
753753 aWriter.AddName('firstName');
754754 FirstName.WriteJson(aWriter);
755755 aWriter.AddName('lastName');
756756 LastName.WriteJson(aWriter);
757757 aWriter.AddName('age');
758758 Age.WriteJson(aWriter);
759759 aWriter.AddName('friends');
760760 Friends.WriteJson(aWriter);
761761 aWriter.AddName('favoriteFruit');
762762 FavoriteFruit.WriteJson(aWriter);
763763 aWriter.EndObject;
764764end;
765765
766766end.
767767```
768768
769769##### Schema inference
770770
771771Also added support for inferring JTD schemas from JSON samples.
772772
773773At least the examples from [this article](https://jsontypedef.com/docs/jtd-infer/) run as expected.
774774
775775For example, this simple program:
776776
777777``` pascal
778778program infer_test;
779779
780780{$mode objfpc}{$H+}
781781
782782uses
783783 SysUtils, lgUtils, lgJson, lgJtdInfer;
784784
785785var
786786 Schema: specialize TGUniqRef<TJsonNode>;
787787
788788const
789789 Lincoln = '{"name":"Abraham Lincoln","isAdmin": true}';
790790 Sherman = '{"name":"William Sherman","isAdmin": false,"middleName": "Tecumseh"}';
791791
792792begin
793793 Schema.Instance := TJtdInferrer.Infer([Lincoln, Sherman], []);
794794 WriteLn(Schema.Instance.FormatJson(DefaultJsonFmtStyle));
795795end.
796796```
797797
798798generates such a schema:
799799
800800``` json
801801{
802802 "properties": {
803803 "name": {"type": "string"},
804804 "isAdmin": {"type": "boolean"}
805805 },
806806 "optionalProperties": {
807807 "middleName": {"type": "string"}
808808 }
809809}
810810```
811811
812812The /example/json/jtd folder contains a small schema editor to demonstrate the functionality of JSON Typedef.
813813
814814## Support for JSONPath
815815
816816#### About JSONPath
817817
818818
819819JSONPath enables you to search for particular data in a JSON document using a simple query syntax somewhat similar to XPath.
820820
821821It is based on Stefan Gessner's popular proposal( [2007-02-21](https://goessner.net/articles/JsonPath/)), and now finally achieved Proposed Standard status: [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535) .
822822
823823A JSONPath query might look like this:
824824
825-`$.store.book[?@.price < 10].title`
825+```text
826+$.store.book[?@.price < 10].title
827+```
826828
827829or
828830
829-`$['store']['book'][?@['price'] < 10]['title']`
831+```text
832+$['store']['book'][?@['price'] < 10]['title']
833+```
830834
831835or
832836
833-`$["store"]["book"][?@["price"] < 10]["title"]`
837+```text
838+$["store"]["book"][?@["price"] < 10]["title"]
839+```
834840
835841These are just different ways of writing a query to select book titles which price is less than 10.
836842
837843#### Syntax
838844
839845The implementation of JSONPath in LGenerics tries to follow the syntax described in the [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535).
840846
841847Any query starts with \$ sign followed by some (possibly zero) number of segments, leading and trailing spaces are not allowed.
842848
843849The whole process can be depicted as a pipe, where each step selects some number of child nodes ( possibly zero), which are then passed to the next step. The result of this is a list of Nodes, Nodelist in terms of specifications.
844850
845851It is worth noting that in specs terminology, a JSONPath Node represents a pair, the JSON value along with its location within the query argument.
846852
847853| Element | Descrition |
848854|----|----|
849855| \$ | root node identifier (query argument) |
850856| @ | current node identifier |
851857| \[\<selectors\>\] | child segment: selects zero or more children of a node; contains one or more selectors, separated by commas |
852858| .name | shorthand for \['name'\] or \["name"\] |
853859| .* | shorthand for \[*\] |
854860| ..\[\<selectors\>\] | descendant segment: selects zero or more descendants of a node; contains one or more selectors, separated by commas |
855861| ..name | shorthand for ..\['name'\] |
856862| ..* | shorthand for ..\[*\] |
857863| 'name' | name selector: selects a named child of an object |
858864| * | wildcard selector: selects all children of a node |
859865| 7 | index selector: selects an indexed child of an array (0-based) |
860866| 0:42:5 | array slice selector: start:end:step for arrays |
861867| ?\<logical-expr\> | filter selector: selects particular children using a logical expression |
862868| length(@.foo) | function extension: invokes a function in a filter expression |
863869
864870Basic syntax elements
865871
866872Index and slice selectors have certain peculiarities:
867873
868874:* `$.book[1]` matches the second book
869875
870876:* `$.book[-1]` matches the last book
871877
872878:* `$.book[2:]` matches the third and all following books
873879
874880:* `$.book[-2:]` matches the last two books
875881
876882:* `$.book[:2]` matches the first two books
877883
878884:* `$.book[::]` matches all books
879885
880886:* `$.book[::-1]` matches all books in reverse order
881887
882888The filter selector is a logical expression containing logical operators, comparison operators, and function calls.
883889
884890| Precedence | Operator type | Syntax |
885891|------------|---------------|--------------------------|
886892| 5 | Grouping | ( ... ) |
887893| 4 | Logical NOT | ! |
888894| 3 | Relations | ==, !=, \>, \<, \>=, \<= |
889895| 2 | Logical AND | && |
890896| 1 | Logical OR | \<nowiki\> |
891897
892898Filter expressions operator precedence
893899
894900It is also highly recommended to implement 5 functions whose syntax and semantics are predefined in the specifications:
895901
896902- `length()`
897903- `count()`
898904- `match()`
899905- `search()`
900906- `value()`
901907
902908The match() and search() functions use a special flavor of regular expression (I-Regexp, [RFC 9485](https://www.rfc-editor.org/rfc/rfc9485)) for interoperability purposes.
903909
904910#### Implementation
905911
906912In LGenerics, the JSONPath implementation can be found in the LgJsonPath unit.
907913
908914JSONPath functionality is represented by the IJsonPath COM interface, which currently (end of February 2024) contains 12 basic matching methods for different types of input and output data and an additional Params\[\] property for setting parameters in parametric queries.
909915
910916To get a reference to the IJsonPath interface, one can use an overloaded version of the JpParseQuery() function with the required set of parameters.
911917
912918In addition to the built-in functions defined in the specification, some extra functions of greater or lesser utility have been added; a list and description can be found in the unit header.
913919
914920It is also possible to register user-defined functions, which, however, requires some caution.
915921
916922The specs seems to be realized in its entirety and the implementation is successfully passing the current compliance tests.
917923
918924A small example, JSON document taken from the specifications:
919925
920926``` pascal
921927program test_jp;
922928{$mode objfpc}{$h+}
923929uses
924930 SysUtils, LgUtils, LgJson, lgJsonPath;
925931
926932const
927933 Json: string =
928934 '{ "store": { ' +
929935 ' "book": [ ' +
930936 ' { "category": "reference", ' +
931937 ' "author": "Nigel Rees", ' +
932938 ' "title": "Sayings of the Century",' +
933939 ' "price": 8.95 ' +
934940 ' }, ' +
935941 ' { "category": "fiction", ' +
936942 ' "author": "Evelyn Waugh", ' +
937943 ' "title": "Sword of Honour", ' +
938944 ' "price": 12.99 ' +
939945 ' }, ' +
940946 ' { "category": "fiction", ' +
941947 ' "author": "Herman Melville", ' +
942948 ' "title": "Moby Dick", ' +
943949 ' "isbn": "0-553-21311-3", ' +
944950 ' "price": 8.99 ' +
945951 ' }, ' +
946952 ' { "category": "fiction", ' +
947953 ' "author": "J. R. R. Tolkien", ' +
948954 ' "title": "The Lord of the Rings", ' +
949955 ' "isbn": "0-395-19395-8", ' +
950956 ' "price": 22.99 ' +
951957 ' } ' +
952958 ' ], ' +
953959 ' "bicycle": { ' +
954960 ' "color": "red", ' +
955961 ' "price": 399 ' +
956962 ' } ' +
957963 ' } ' +
958964 '} ';
959965
960966var
961967 Doc, CurrNode: specialize TGAutoRef<TJsonNode>;
962968 Path: IJsonPath;
963969 List: TJpNodeList;
964970 Node: TJpNode;
965971begin
966972 if not Doc.Instance.Parse(Json) then begin
967973 WriteLn('Oops, failed to parse document');
968974 Halt;
969975 end;
970976
971977 if JpParseQuery('$..author', Path) then begin
972978 WriteLn('All authors: ');
973979 List := Path.Match(Doc.Instance);
974980 for Node in List do begin
975981 CurrNode.Instance.Parse(Node.AsJson);
976982 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
977983 end;
978984 WriteLn;
979985 end;
980986
981987 if JpParseQuery('$..[?@.isbn]', Path) then begin
982988 WriteLn('Anything that has an ISBN:');
983989 List := Path.Match(Doc.Instance);
984990 for Node in List do begin
985991 CurrNode.Instance.Parse(Node.AsJson);
986992 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
987993 end;
988994 WriteLn;
989995 end;
990996
991997 if JpParseQuery('$..[?@.color == "red"]', Path) then begin
992998 WriteLn('Anything that is red: ');
993999 List := Path.Match(Doc.Instance);
9941000 for Node in List do begin
9951001 CurrNode.Instance.Parse(Node.AsJson);
9961002 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
9971003 end;
9981004 WriteLn;
9991005 end;
10001006
10011007 if JpParseQuery('$..[?search(@.title, "[Hh]ono")]', Path) then begin
10021008 WriteLn('Anything that contains "Hono" or "hono" in the title: ');
10031009 List := Path.Match(Doc.Instance);
10041010 for Node in List do begin
10051011 CurrNode.Instance.Parse(Node.AsJson);
10061012 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
10071013 end;
10081014 end;
10091015end.
10101016```
10111017
10121018It prints:
10131019
1014-`All authors:`\
1015-`  {"location": "$['store']['book'][0]['author']", "value": "Nigel Rees"}`\
1016-`  {"location": "$['store']['book'][1]['author']", "value": "Evelyn Waugh"}`\
1017-`  {"location": "$['store']['book'][2]['author']", "value": "Herman Melville"}`\
1018-`  {"location": "$['store']['book'][3]['author']", "value": "J. R. R. Tolkien"}`\
1019-``\
1020-`Anything that has an ISBN:`\
1021-`  {`\
1022-`      "location": "$['store']['book'][2]",`\
1023-`      "value": {`\
1024-`          "category": "fiction",`\
1025-`          "author": "Herman Melville",`\
1026-`          "title": "Moby Dick",`\
1027-`          "isbn": "0-553-21311-3",`\
1028-`          "price": 8.99`\
1029-`      }`\
1030-`  }`\
1031-`  {`\
1032-`      "location": "$['store']['book'][3]",`\
1033-`      "value": {`\
1034-`          "category": "fiction",`\
1035-`          "author": "J. R. R. Tolkien",`\
1036-`          "title": "The Lord of the Rings",`\
1037-`          "isbn": "0-395-19395-8",`\
1038-`          "price": 22.99`\
1039-`      }`\
1040-`  }`\
1041-``\
1042-`Anything that is red:`\
1043-`  {`\
1044-`      "location": "$['store']['bicycle']",`\
1045-`      "value": {"color": "red", "price": 399}`\
1046-`  }`\
1047-``\
1048-`Anything that contains "Hono" or "hono" in the title:`\
1049-`  {`\
1050-`      "location": "$['store']['book'][1]",`\
1051-`      "value": {`\
1052-`          "category": "fiction",`\
1053-`          "author": "Evelyn Waugh",`\
1054-`          "title": "Sword of Honour",`\
1055-`          "price": 12.99`\
1056-`      }`\
1057-`  }`
1020+```text
1021+All authors:
1022+ {"location": "$['store']['book'][0]['author']", "value": "Nigel Rees"}
1023+ {"location": "$['store']['book'][1]['author']", "value": "Evelyn Waugh"}
1024+ {"location": "$['store']['book'][2]['author']", "value": "Herman Melville"}
1025+ {"location": "$['store']['book'][3]['author']", "value": "J. R. R. Tolkien"}
1026+
1027+Anything that has an ISBN:
1028+ {
1029+ "location": "$['store']['book'][2]",
1030+ "value": {
1031+ "category": "fiction",
1032+ "author": "Herman Melville",
1033+ "title": "Moby Dick",
1034+ "isbn": "0-553-21311-3",
1035+ "price": 8.99
1036+ }
1037+ }
1038+ {
1039+ "location": "$['store']['book'][3]",
1040+ "value": {
1041+ "category": "fiction",
1042+ "author": "J. R. R. Tolkien",
1043+ "title": "The Lord of the Rings",
1044+ "isbn": "0-395-19395-8",
1045+ "price": 22.99
1046+ }
1047+ }
1048+
1049+Anything that is red:
1050+ {
1051+ "location": "$['store']['bicycle']",
1052+ "value": {"color": "red", "price": 399}
1053+ }
1054+
1055+Anything that contains "Hono" or "hono" in the title:
1056+ {
1057+ "location": "$['store']['book'][1]",
1058+ "value": {
1059+ "category": "fiction",
1060+ "author": "Evelyn Waugh",
1061+ "title": "Sword of Honour",
1062+ "price": 12.99
1063+ }
1064+ }
1065+```
10581066
10591067## Performance
10601068
10611069### Containers
10621070
10631071#### Performance of hash maps
10641072
10651073There is a [wonderful benchmark](https://www.benibela.de/fpc-map-benchmark_en.html) from *BeniBela*, which also covers hashmaps from LGenerics.
10661074
10671075### Algorithms on arrays/vectors
10681076
10691077#### Sorting
10701078
10711079(This section is outdated.)
10721080
10731081The benchmark sorts 1000000 integers and calculates the number of CPU cycles spent on one element and involves:
10741082
10751083- QuickSort_ItemList_Context from unit SortBase (Rtl/SortBase)
10761084- TOrderingArrayUtils.Sort from Fcl-Stl.GArrayUtils (Fcl-Stl)
10771085- TArrayHelper.Sort from Rtl-Generics.Generics.Collections (Rtl-Generics)
10781086- std::sort from libstdc++ (std::sort)
10791087- std::stable_sort from libstdc++ (std::stable_sort)
10801088- TGOrdinalArrayHelper.QuickSort (LG/QuickSort)
10811089- TGOrdinalArrayHelper.IntroSort (LG/IntroSort)
10821090- TGOrdinalArrayHelper.DualPivotQuickSort (LG/DualPivotQuickSort)
10831091- TGOrdinalArrayHelper.PDQSort (LG/PDQSort)
10841092- TGComparableArrayHelper.MergeSort (LG/MergeSort)
10851093- TGComparableTimSort.Sort (LG/TimSort)
10861094- TGOrdinalArrayHelper.Sort (LG/Sort)
10871095- A negative value indicates that the algorithm exhibits quadratic behavior.
10881096
10891097![](/wiki-images/a/a7/Sort_bench_1000000.png)
10901098
10911099### Algorithms on graphs
10921100
10931101For performance comparison the AGraph library (taken from [here](https://github.com/zamtmn/agraphlaz)) was chosen as a reference implementation. All tests were compiled for x86 and run on win64 machine (unfortunately the 64-bit version of AGraph crashes on these tests).
10941102
10951103So, AGraph vs LGenerics:
10961104
10971105| problem | graph | AGraph time(ms) | LGenerics time(ms) | notes |
10981106|----|----|----|----|----|
10991107| BFS traversal | undirected, V=500000, E=4000000 | 640 | 109 | |
11001108| BFS traversal | directed, V=500000, E=4000000 | 484 | 94 | |
11011109| Connected components | undirected, V=500000, E=4000000 | 764 | 0 | If no edges were removed, TGSimpleGraph always knows the number and ownership of connected components |
11021110| Strongly connected components | directed, V=500000, E=4000000 | 1060 | 265 | |
11031111| Single-source shortest path | undirected, V=500000, E=4000000 | 3011 | 842 | |
11041112| Single-source shortest path | directed, V=500000, E=4000000 | 2153 | 718 | |
11051113| Single-pair shortest path | undirected, V=500000, E=4000000 | 2777 | 31 | |
11061114| Single-pair shortest path | directed, V=500000, E=4000000 | 1825 | 47 | |
11071115| Minimum spanning tree | undirected, V=500000, E=4000000 | 5974 | 640 | |
11081116| Maximum flow | dense, V=1000, E=499500 | 29499 | 47 | |
11091117| Maximum flow | sparse, V=64000, E=512000 | 3339 | 63 | |
11101118| Planarity test | undirected, V=219086, E=657252 | 1419 | 172 | |
11111119| Maximum clique | DIMACS brock200_2.clq, V=200, E=9876 | 84210 | 16 | |
11121120| Maximum clique | DIMACS brock200_4.clq, V=200, E=13089 | Cancelled(timeout 1 h) | 312 | |
11131121
11141122### Algorithms on strings/sequences
11151123
11161124It was curious to compare the performance of the SimRatioLevEx() function (which is inspired by FuzzyWuzzy) with some incarnations of the FuzzyWuzzy ([listed here](https://pypi.org/project/fuzzywuzzy/)) on benchmark datasets. Disclamer: SimRatioLevEx() does not reproduce FuzzyWuzzy, but it does some things in a similar way, in particular, SimRatioLevEx() in smTokenSetEx mode and token_set_ratio() do roughly the same job.
11171125
11181126It seems the C++ version only supports single-byte strings, so only compared to the single-byte version of SimRatioLevEx():
11191127
11201128 Dataset SimRatioLevEx() token_set_ratio()
11211129
11221130 Short/big_dist 1154 6440
11231131 Short/small_dist 967 3020
11241132 Medium/big_dist 811 3450
11251133 Medium/small_dist 702 1470
11261134 Long/big_dist 1966 15000
11271135 Long/small_dist 1061 2250
11281136
11291137The numbers indicate the run time in milliseconds; the C++ version was compiled with gcc-8.1.0 with options -O3 -m64; the Pascal version was compiled with FPC-3.3.1-9941-g8e6e9bbf33, -O3. The benchmark was run on a Windows x64 machine.
11301138
11311139The Go version, on the contrary, always works with Unicode strings:
11321140
11331141 Dataset SimRatioLevExUtf8() TokenSetRatio()
11341142
11351143 Short/big_dist 2143 18705
11361144 Short/small_dist 1593 2224
11371145 Medium/big_dist 1266 15062
11381146 Medium/small_dist 853 1742
11391147 Long/big_dist 3853 79851
11401148 Long/small_dist 1269 3126
11411149
11421150Go version: go1.10.4 linux/amd64; FPC-3.3.1-10683-g2a19e152b7 -O3. The benchmark was run on a virtual Linux machine.
11431151
11441152### Performance of JSON operations
11451153
11461154A small performance comparison of the lgJson. TJsonNode class with some other implementations, the benchmark was compiled for x86_64 and run on a win64 machine.
11471155
11481156| description | FpJson | JsonTools | lgJson | notes |
11491157|----|----|----|----|----|
11501158| Average parsing speed on a set of JSON samples from the example/json/parse_bench folder, MB/s | 34,45 | 46,11 | 68,12 | |
11511159| Average validating speed on a set of JSON samples from the example/json/parse_bench folder, MB/s | 34,4 | 46,02 | 349,48 | FpJson does not provide any function to validate JSON, so a parser was used |
11521160| Recursive traversal (using the built-in enumerator) of the document of moderate size (34 KB) with all values read (50000 repeats), milliseconds | 8315 | 6802 | 639 | |
11531161| Search in a moderately sized document using path (750000 repeats), milliseconds | 8549 | 5398 | 873 | |
11541162| Dynamic creation of a small document and reading it as formatted JSON (150000 repeats), milliseconds | 7597 | 4150 | 1903 | |
11551163| Saving a moderate size document to a stream (10000 repeats), milliseconds | 4820 | 4587 | 827 | |
11561164| RFC 8259 compliance test, 295 test files from test/json_testset/testset | 5 failed | 23 failed | all passed | |
11571165
11581166#### Performance of export to JSON
11591167
11601168Datasets used: a collection of simple items (with three string properties and one integer property) and an array of records whose fields contain values that coincide with the values of the collection's item properties (i.e. the resulting JSONs should be the same). The number of elements in the datasets was chosen so that the size of the resulting JSON approximately corresponded to the values (20KB, 100KB, 1MB, 10MB, 50MB).
11611169
11621170TJSONStreamer.CollectionToJSON() was taken as a reference implementation (FpJsonStreamer for short), other members:
11631171
11641172- PdoToJson() with a collection as parameter (PdoCollection);
11651173- PdoToJson() with array as parameter and field name list registration (PdoRecordFieldMap);
11661174- PdoToJson() with array as parameter and custom callback registration (PdoRecordCallback);
11671175
11681176Each member exports the data several times (number of attempts is pre-defined), the best time is taken as the result.
11691177
11701178The full benchmark code can be found in the folder LGenerics/example/json/json_export_bench.
11711179
11721180Results on author's machine:
11731181
11741182 -------<JSON size 20 KB>-------
11751183 FpJsonStreamer......1,045 ms.
11761184 Rating: 22,23 MB/s.
11771185 PdoCollection.......0,123 ms.
11781186 Rating: 164,36 MB/s.
11791187 PdoRecordFieldMap...0,089 ms.
11801188 Rating: 226,76 MB/s.
11811189 PdoRecordCallback...0,057 ms.
11821190 Rating: 356,65 MB/s.
11831191 -------<JSON size 100 KB>-------
11841192 FpJsonStreamer......5,647 ms.
11851193 Rating: 20,51 MB/s.
11861194 PdoCollection.......0,626 ms.
11871195 Rating: 160,7 MB/s.
11881196 PdoRecordFieldMap...0,443 ms.
11891197 Rating: 226,72 MB/s.
11901198 PdoRecordCallback...0,285 ms.
11911199 Rating: 352,29 MB/s.
11921200 -------<JSON size 1 MB>-------
11931201 FpJsonStreamer......80,298 ms.
11941202 Rating: 14,34 MB/s.
11951203 PdoCollection.......6,552 ms.
11961204 Rating: 152,63 MB/s.
11971205 PdoRecordFieldMap...4,701 ms.
11981206 Rating: 212,71 MB/s.
11991207 PdoRecordCallback...3,107 ms.
12001208 Rating: 321,85 MB/s.
12011209 -------<JSON size 10 MB>-------
12021210 FpJsonStreamer......3554,483 ms.
12031211 Rating: 3,24 MB/s.
12041212 PdoCollection.......71,202 ms.
12051213 Rating: 140,57 MB/s.
12061214 PdoRecordFieldMap...53,309 ms.
12071215 Rating: 187,75 MB/s.
12081216 PdoRecordCallback...37,39 ms.
12091217 Rating: 267,69 MB/s.
12101218 -------<JSON size 50 MB>-------
12111219 FpJsonStreamer......84343,47 ms.
12121220 Rating: 0,68 MB/s.
12131221 PdoCollection.......350,224 ms.
12141222 Rating: 142,99 MB/s.
12151223 PdoRecordFieldMap...260,963 ms.
12161224 Rating: 191,89 MB/s.
12171225 PdoRecordCallback...183,328 ms.
12181226 Rating: 273,16 MB/s.
12191227
12201228#### Performance of JSON Type Definition
12211229
12221230Test schema:
12231231
12241232``` json
12251233{
12261234 "definitions": {
12271235 "point": {
12281236 "properties": {
12291237 "x": {"type": "int32"},
12301238 "y": {"type": "int32"}
12311239 }
12321240 },
12331241 "shape": {"enum": ["circle", "ellipce", "rectangle", "rhombus", "triangle"]},
12341242 "figure": {
12351243 "properties": {
12361244 "name": {"type": "string"},
12371245 "shape": {"ref": "shape"},
12381246 "area": {"type": "float64"},
12391247 "center": {"ref": "point"}
12401248 }
12411249 }
12421250 },
12431251 "elements": {"ref": "figure"}
12441252}
12451253```
12461254
12471255The TJSONDeStreamer loading a collection of figures was taken as a reference implementation. To create more or less extreme conditions, a test JSON of size 62267256 bytes was generated, JTD class(unit Figures) was generated using TJtdPasCodegen.
12481256
12491257Full source code of the test application:
12501258
12511259``` pascal
12521260program jtd_test;
12531261
12541262{$mode objfpc}{$H+}
12551263
12561264uses
12571265 Classes, SysUtils, fpJsonRtti, lgJson, lgJsonTypeDef, Figures;
12581266
12591267type
12601268{$PUSH}{$M+}
12611269 TMyPoint = class
12621270 private
12631271 FX,
12641272 FY: Integer;
12651273 published
12661274 property x: Integer read FX write FX;
12671275 property y: Integer read FY write FY;
12681276 end;
12691277{$POP}
12701278
12711279 TMyFigure = class(TCollectionItem)
12721280 private
12731281 FArea: Double;
12741282 FCenter: TMyPoint;
12751283 FName: string;
12761284 FShape: TShape;
12771285 public
12781286 constructor Create(ACollection: TCollection); override;
12791287 destructor Destroy; override;
12801288 published
12811289 property name: string read FName write FName;
12821290 property shape: TShape read FShape write FShape;
12831291 property area: Double read FArea write FArea;
12841292 property center: TMyPoint read FCenter;
12851293 end;
12861294
12871295constructor TMyFigure.Create(ACollection: TCollection);
12881296begin
12891297 inherited Create(ACollection);
12901298 FCenter := TMyPoint.Create;
12911299end;
12921300
12931301destructor TMyFigure.Destroy;
12941302begin
12951303 FCenter.Free;
12961304 inherited;
12971305end;
12981306
12991307var
13001308 s: string;
13011309
13021310procedure TestCollection;
13031311var
13041312 c: TCollection;
13051313 Start, Stop: QWord;
13061314begin
13071315 c := TCollection.Create(TMyFigure);
13081316 Start := GetTickCount64;
13091317 with TJSONDeStreamer.Create(nil) do
13101318 try
13111319 JSONToCollection(s, c);
13121320 finally
13131321 Free;
13141322 end;
13151323 Stop := GetTickCount64;
13161324 c.Free;
13171325 WriteLn('TJSONDeStreamer.JSONToCollection: ', Stop - Start);
13181326end;
13191327
13201328procedure TestJtd;
13211329var
13221330 Node: TJsonNode;
13231331 Schema: TJtdSchema;
13241332 eList: TJtdErrorList;
13251333 Figures: TFigureList;
13261334 Start, Stop: QWord;
13271335begin
13281336 Start := GetTickCount64;
13291337 if not TJsonNode.TryParse(s, Node) then begin
13301338 WriteLn('Failed to load data');
13311339 exit;
13321340 end;
13331341 Stop := GetTickCount64;
13341342 WriteLn('Loading data into TJsonNode: ', Stop - Start);
13351343
13361344 if not TJtdSchema.TryLoadFromFile('schema.json', Schema) then begin
13371345 WriteLn('Failed to load schema');
13381346 exit;
13391347 end;
13401348
13411349 Start := GetTickCount64;
13421350 if Validate(Node, Schema, eList) <> jvrOk then begin
13431351 WriteLn('Failed to validate data');
13441352 exit;
13451353 end;
13461354 Stop := GetTickCount64;
13471355 Schema.Free;
13481356 WriteLn('TJsonNode data validation: ', Stop - Start);
13491357
13501358 Start := GetTickCount64;
13511359 Figures := TFigureList.ReadJson(Node) as TFigureList;
13521360 Stop := GetTickCount64;
13531361 Figures.Free;
13541362 Node.Free;
13551363 WriteLn('Loadind JTD class from TJsonNode: ', Stop - Start);
13561364
13571365 Start := GetTickCount64;
13581366 Figures := TFigureList.ReadJson(s) as TFigureList;
13591367 Stop := GetTickCount64;
13601368 Figures.Free;
13611369 WriteLn('Direct loading of JTD class: ', Stop - Start);
13621370end;
13631371
13641372procedure LoadData;
13651373begin
13661374 with TStringStream.Create do
13671375 try
13681376 LoadFromFile('data.json');
13691377 s := DataString;
13701378 finally
13711379 Free;
13721380 end;
13731381end;
13741382
13751383begin
13761384 LoadData;
13771385
13781386 TestCollection;
13791387 TestJtd;
13801388
13811389end.
13821390```
13831391
13841392On my machine, it prints results like this:
13851393
13861394 TJSONDeStreamer.JSONToCollection: 5304
13871395 Loading data into TJsonNode: 1513
13881396 TJsonNode data validation: 1732
13891397 Loadind JTD class from TJsonNode: 515
13901398 Direct loading of JTD class: 1560
13911399
13921400Conclusion: the total validation and loading time of the JTD class is still noticeably better than the TCollection load time (3760 vs 5304).
13931401
13941402The direct load time of the JTD class is better than the load time of the TCollection by more than three times.
13951403
13961404---
13971405*From the Free Pascal wiki, page "LGenerics", as it stood on 2024-05-14 after a change by Avk, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
13981406Category: JSON
13991407Category: Libraries

Back to history