Sign in Join
Wiki copytemporary

LGenerics: what changed

Revision 2 to 3. 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
825825`$.store.book[?@.price < 10].title`
826826
827827or
828828
829829`$['store']['book'][?@['price'] < 10]['title']`
830830
831831or
832832
833833`$["store"]["book"][?@["price"] < 10]["title"]`
834834
835835These are just different ways of writing a query to select book titles which price is less than 10.
836836
837837#### Syntax
838838
839839The implementation of JSONPath in LGenerics tries to follow the syntax described in the [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535).
840840
841841Any query starts with \$ sign followed by some (possibly zero) number of segments, leading and trailing spaces are not allowed.
842842
843843The 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.
844844
845845It is worth noting that in specs terminology, a JSONPath Node represents a pair, the JSON value along with its location within the query argument.
846846
847847| Element | Descrition |
848848|----|----|
849849| \$ | root node identifier (query argument) |
850850| @ | current node identifier |
851851| \[\<selectors\>\] | child segment: selects zero or more children of a node; contains one or more selectors, separated by commas |
852852| .name | shorthand for \['name'\] or \["name"\] |
853853| .* | shorthand for \[*\] |
854854| ..\[\<selectors\>\] | descendant segment: selects zero or more descendants of a node; contains one or more selectors, separated by commas |
855855| ..name | shorthand for ..\['name'\] |
856856| ..* | shorthand for ..\[*\] |
857857| 'name' | name selector: selects a named child of an object |
858858| * | wildcard selector: selects all children of a node |
859859| 7 | index selector: selects an indexed child of an array (0-based) |
860860| 0:42:5 | array slice selector: start:end:step for arrays |
861861| ?\<logical-expr\> | filter selector: selects particular children using a logical expression |
862862| length(@.foo) | function extension: invokes a function in a filter expression |
863863
864864Basic syntax elements
865865
866866Index and slice selectors have certain peculiarities:
867867
868868:* `$.book[1]` matches the second book
869869
870870:* `$.book[-1]` matches the last book
871871
872872:* `$.book[2:]` matches the third and all following books
873873
874874:* `$.book[-2:]` matches the last two books
875875
876876:* `$.book[:2]` matches the first two books
877877
878878:* `$.book[::]` matches all books
879879
880880:* `$.book[::-1]` matches all books in reverse order
881881
882882The filter selector is a logical expression containing logical operators, comparison operators, and function calls.
883883
884884| Precedence | Operator type | Syntax |
885885|------------|---------------|--------------------------|
886886| 5 | Grouping | ( ... ) |
887887| 4 | Logical NOT | ! |
888888| 3 | Relations | ==, !=, \>, \<, \>=, \<= |
889889| 2 | Logical AND | && |
890890| 1 | Logical OR | \<nowiki\> |
891891
892892Filter expressions operator precedence
893893
894894It is also highly recommended to implement 5 functions whose syntax and semantics are predefined in the specifications:
895895
896896- `length()`
897897- `count()`
898898- `match()`
899899- `search()`
900900- `value()`
901901
902902The 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.
903903
904904#### Implementation
905905
906906In LGenerics, the JSONPath implementation can be found in the LgJsonPath unit.
907907
908908JSONPath 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.
909909
910910To get a reference to the IJsonPath interface, one can use an overloaded version of the JpParseQuery() function with the required set of parameters.
911911
912912In 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.
913913
914914It is also possible to register user-defined functions, which, however, requires some caution.
915915
916916The specs seems to be realized in its entirety and the implementation is successfully passing the current compliance tests.
917917
918918A small example, JSON document taken from the specifications:
919919
920920``` pascal
921921program test_jp;
922922{$mode objfpc}{$h+}
923923uses
924924 SysUtils, LgUtils, LgJson, lgJsonPath;
925925
926926const
927927 Json: string =
928928 '{ "store": { ' +
929929 ' "book": [ ' +
930930 ' { "category": "reference", ' +
931931 ' "author": "Nigel Rees", ' +
932932 ' "title": "Sayings of the Century",' +
933933 ' "price": 8.95 ' +
934934 ' }, ' +
935935 ' { "category": "fiction", ' +
936936 ' "author": "Evelyn Waugh", ' +
937937 ' "title": "Sword of Honour", ' +
938938 ' "price": 12.99 ' +
939939 ' }, ' +
940940 ' { "category": "fiction", ' +
941941 ' "author": "Herman Melville", ' +
942942 ' "title": "Moby Dick", ' +
943943 ' "isbn": "0-553-21311-3", ' +
944944 ' "price": 8.99 ' +
945945 ' }, ' +
946946 ' { "category": "fiction", ' +
947947 ' "author": "J. R. R. Tolkien", ' +
948948 ' "title": "The Lord of the Rings", ' +
949949 ' "isbn": "0-395-19395-8", ' +
950950 ' "price": 22.99 ' +
951951 ' } ' +
952952 ' ], ' +
953953 ' "bicycle": { ' +
954954 ' "color": "red", ' +
955955 ' "price": 399 ' +
956956 ' } ' +
957957 ' } ' +
958958 '} ';
959959
960960var
961961 Doc, CurrNode: specialize TGAutoRef<TJsonNode>;
962962 Path: IJsonPath;
963963 List: TJpNodeList;
964964 Node: TJpNode;
965965begin
966966 if not Doc.Instance.Parse(Json) then begin
967967 WriteLn('Oops, failed to parse document');
968968 Halt;
969969 end;
970970
971971 if JpParseQuery('$..author', Path) then begin
972972 WriteLn('All authors: ');
973973 List := Path.Match(Doc.Instance);
974974 for Node in List do begin
975975 CurrNode.Instance.Parse(Node.AsJson);
976976 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
977977 end;
978978 WriteLn;
979979 end;
980980
981981 if JpParseQuery('$..[?@.isbn]', Path) then begin
982982 WriteLn('Anything that has an ISBN:');
983983 List := Path.Match(Doc.Instance);
984984 for Node in List do begin
985985 CurrNode.Instance.Parse(Node.AsJson);
986986 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
987987 end;
988988 WriteLn;
989989 end;
990990
991991 if JpParseQuery('$..[?@.color == "red"]', Path) then begin
992992 WriteLn('Anything that is red: ');
993993 List := Path.Match(Doc.Instance);
994994 for Node in List do begin
995995 CurrNode.Instance.Parse(Node.AsJson);
996996 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
997997 end;
998998 WriteLn;
999999 end;
10001000
10011001 if JpParseQuery('$..[?search(@.title, "[Hh]ono")]', Path) then begin
10021002 WriteLn('Anything that contains "Hono" or "hono" in the title: ');
10031003 List := Path.Match(Doc.Instance);
10041004 for Node in List do begin
10051005 CurrNode.Instance.Parse(Node.AsJson);
10061006 WriteLn(CurrNode.Instance.FormatJson(DefaultJsonFmtStyle, 2));
10071007 end;
10081008 end;
10091009end.
10101010```
10111011
10121012It prints:
10131013
10141014`All authors:`\
10151015`  {"location": "$['store']['book'][0]['author']", "value": "Nigel Rees"}`\
10161016`  {"location": "$['store']['book'][1]['author']", "value": "Evelyn Waugh"}`\
10171017`  {"location": "$['store']['book'][2]['author']", "value": "Herman Melville"}`\
10181018`  {"location": "$['store']['book'][3]['author']", "value": "J. R. R. Tolkien"}`\
10191019``\
10201020`Anything that has an ISBN:`\
10211021`  {`\
10221022`      "location": "$['store']['book'][2]",`\
10231023`      "value": {`\
10241024`          "category": "fiction",`\
10251025`          "author": "Herman Melville",`\
10261026`          "title": "Moby Dick",`\
10271027`          "isbn": "0-553-21311-3",`\
10281028`          "price": 8.99`\
10291029`      }`\
10301030`  }`\
10311031`  {`\
10321032`      "location": "$['store']['book'][3]",`\
10331033`      "value": {`\
10341034`          "category": "fiction",`\
10351035`          "author": "J. R. R. Tolkien",`\
10361036`          "title": "The Lord of the Rings",`\
10371037`          "isbn": "0-395-19395-8",`\
10381038`          "price": 22.99`\
10391039`      }`\
10401040`  }`\
10411041``\
10421042`Anything that is red:`\
10431043`  {`\
10441044`      "location": "$['store']['bicycle']",`\
10451045`      "value": {"color": "red", "price": 399}`\
10461046`  }`\
10471047``\
10481048`Anything that contains "Hono" or "hono" in the title:`\
10491049`  {`\
10501050`      "location": "$['store']['book'][1]",`\
10511051`      "value": {`\
10521052`          "category": "fiction",`\
10531053`          "author": "Evelyn Waugh",`\
10541054`          "title": "Sword of Honour",`\
10551055`          "price": 12.99`\
10561056`      }`\
10571057`  }`
10581058
10591059## Performance
10601060
10611061### Containers
10621062
10631063#### Performance of hash maps
10641064
10651065There is a [wonderful benchmark](https://www.benibela.de/fpc-map-benchmark_en.html) from *BeniBela*, which also covers hashmaps from LGenerics.
10661066
10671067### Algorithms on arrays/vectors
10681068
10691069#### Sorting
10701070
10711071(This section is outdated.)
10721072
10731073The benchmark sorts 1000000 integers and calculates the number of CPU cycles spent on one element and involves:
10741074
10751075- QuickSort_ItemList_Context from unit SortBase (Rtl/SortBase)
10761076- TOrderingArrayUtils.Sort from Fcl-Stl.GArrayUtils (Fcl-Stl)
10771077- TArrayHelper.Sort from Rtl-Generics.Generics.Collections (Rtl-Generics)
10781078- std::sort from libstdc++ (std::sort)
10791079- std::stable_sort from libstdc++ (std::stable_sort)
10801080- TGOrdinalArrayHelper.QuickSort (LG/QuickSort)
10811081- TGOrdinalArrayHelper.IntroSort (LG/IntroSort)
10821082- TGOrdinalArrayHelper.DualPivotQuickSort (LG/DualPivotQuickSort)
10831083- TGOrdinalArrayHelper.PDQSort (LG/PDQSort)
10841084- TGComparableArrayHelper.MergeSort (LG/MergeSort)
10851085- TGComparableTimSort.Sort (LG/TimSort)
10861086- TGOrdinalArrayHelper.Sort (LG/Sort)
10871087- A negative value indicates that the algorithm exhibits quadratic behavior.
10881088
1089-<img src="sort_bench_1000000.png" width="800" />
1089+![](/wiki-images/a/a7/Sort_bench_1000000.png)
10901090
10911091### Algorithms on graphs
10921092
10931093For 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).
10941094
10951095So, AGraph vs LGenerics:
10961096
10971097| problem | graph | AGraph time(ms) | LGenerics time(ms) | notes |
10981098|----|----|----|----|----|
10991099| BFS traversal | undirected, V=500000, E=4000000 | 640 | 109 | |
11001100| BFS traversal | directed, V=500000, E=4000000 | 484 | 94 | |
11011101| Connected components | undirected, V=500000, E=4000000 | 764 | 0 | If no edges were removed, TGSimpleGraph always knows the number and ownership of connected components |
11021102| Strongly connected components | directed, V=500000, E=4000000 | 1060 | 265 | |
11031103| Single-source shortest path | undirected, V=500000, E=4000000 | 3011 | 842 | |
11041104| Single-source shortest path | directed, V=500000, E=4000000 | 2153 | 718 | |
11051105| Single-pair shortest path | undirected, V=500000, E=4000000 | 2777 | 31 | |
11061106| Single-pair shortest path | directed, V=500000, E=4000000 | 1825 | 47 | |
11071107| Minimum spanning tree | undirected, V=500000, E=4000000 | 5974 | 640 | |
11081108| Maximum flow | dense, V=1000, E=499500 | 29499 | 47 | |
11091109| Maximum flow | sparse, V=64000, E=512000 | 3339 | 63 | |
11101110| Planarity test | undirected, V=219086, E=657252 | 1419 | 172 | |
11111111| Maximum clique | DIMACS brock200_2.clq, V=200, E=9876 | 84210 | 16 | |
11121112| Maximum clique | DIMACS brock200_4.clq, V=200, E=13089 | Cancelled(timeout 1 h) | 312 | |
11131113
11141114### Algorithms on strings/sequences
11151115
11161116It 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.
11171117
11181118It seems the C++ version only supports single-byte strings, so only compared to the single-byte version of SimRatioLevEx():
11191119
11201120 Dataset SimRatioLevEx() token_set_ratio()
11211121
11221122 Short/big_dist 1154 6440
11231123 Short/small_dist 967 3020
11241124 Medium/big_dist 811 3450
11251125 Medium/small_dist 702 1470
11261126 Long/big_dist 1966 15000
11271127 Long/small_dist 1061 2250
11281128
11291129The 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.
11301130
11311131The Go version, on the contrary, always works with Unicode strings:
11321132
11331133 Dataset SimRatioLevExUtf8() TokenSetRatio()
11341134
11351135 Short/big_dist 2143 18705
11361136 Short/small_dist 1593 2224
11371137 Medium/big_dist 1266 15062
11381138 Medium/small_dist 853 1742
11391139 Long/big_dist 3853 79851
11401140 Long/small_dist 1269 3126
11411141
11421142Go version: go1.10.4 linux/amd64; FPC-3.3.1-10683-g2a19e152b7 -O3. The benchmark was run on a virtual Linux machine.
11431143
11441144### Performance of JSON operations
11451145
11461146A 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.
11471147
11481148| description | FpJson | JsonTools | lgJson | notes |
11491149|----|----|----|----|----|
11501150| Average parsing speed on a set of JSON samples from the example/json/parse_bench folder, MB/s | 34,45 | 46,11 | 68,12 | |
11511151| 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 |
11521152| 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 | |
11531153| Search in a moderately sized document using path (750000 repeats), milliseconds | 8549 | 5398 | 873 | |
11541154| Dynamic creation of a small document and reading it as formatted JSON (150000 repeats), milliseconds | 7597 | 4150 | 1903 | |
11551155| Saving a moderate size document to a stream (10000 repeats), milliseconds | 4820 | 4587 | 827 | |
11561156| RFC 8259 compliance test, 295 test files from test/json_testset/testset | 5 failed | 23 failed | all passed | |
11571157
11581158#### Performance of export to JSON
11591159
11601160Datasets 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).
11611161
11621162TJSONStreamer.CollectionToJSON() was taken as a reference implementation (FpJsonStreamer for short), other members:
11631163
11641164- PdoToJson() with a collection as parameter (PdoCollection);
11651165- PdoToJson() with array as parameter and field name list registration (PdoRecordFieldMap);
11661166- PdoToJson() with array as parameter and custom callback registration (PdoRecordCallback);
11671167
11681168Each member exports the data several times (number of attempts is pre-defined), the best time is taken as the result.
11691169
11701170The full benchmark code can be found in the folder LGenerics/example/json/json_export_bench.
11711171
11721172Results on author's machine:
11731173
11741174 -------<JSON size 20 KB>-------
11751175 FpJsonStreamer......1,045 ms.
11761176 Rating: 22,23 MB/s.
11771177 PdoCollection.......0,123 ms.
11781178 Rating: 164,36 MB/s.
11791179 PdoRecordFieldMap...0,089 ms.
11801180 Rating: 226,76 MB/s.
11811181 PdoRecordCallback...0,057 ms.
11821182 Rating: 356,65 MB/s.
11831183 -------<JSON size 100 KB>-------
11841184 FpJsonStreamer......5,647 ms.
11851185 Rating: 20,51 MB/s.
11861186 PdoCollection.......0,626 ms.
11871187 Rating: 160,7 MB/s.
11881188 PdoRecordFieldMap...0,443 ms.
11891189 Rating: 226,72 MB/s.
11901190 PdoRecordCallback...0,285 ms.
11911191 Rating: 352,29 MB/s.
11921192 -------<JSON size 1 MB>-------
11931193 FpJsonStreamer......80,298 ms.
11941194 Rating: 14,34 MB/s.
11951195 PdoCollection.......6,552 ms.
11961196 Rating: 152,63 MB/s.
11971197 PdoRecordFieldMap...4,701 ms.
11981198 Rating: 212,71 MB/s.
11991199 PdoRecordCallback...3,107 ms.
12001200 Rating: 321,85 MB/s.
12011201 -------<JSON size 10 MB>-------
12021202 FpJsonStreamer......3554,483 ms.
12031203 Rating: 3,24 MB/s.
12041204 PdoCollection.......71,202 ms.
12051205 Rating: 140,57 MB/s.
12061206 PdoRecordFieldMap...53,309 ms.
12071207 Rating: 187,75 MB/s.
12081208 PdoRecordCallback...37,39 ms.
12091209 Rating: 267,69 MB/s.
12101210 -------<JSON size 50 MB>-------
12111211 FpJsonStreamer......84343,47 ms.
12121212 Rating: 0,68 MB/s.
12131213 PdoCollection.......350,224 ms.
12141214 Rating: 142,99 MB/s.
12151215 PdoRecordFieldMap...260,963 ms.
12161216 Rating: 191,89 MB/s.
12171217 PdoRecordCallback...183,328 ms.
12181218 Rating: 273,16 MB/s.
12191219
12201220#### Performance of JSON Type Definition
12211221
12221222Test schema:
12231223
12241224``` json
12251225{
12261226 "definitions": {
12271227 "point": {
12281228 "properties": {
12291229 "x": {"type": "int32"},
12301230 "y": {"type": "int32"}
12311231 }
12321232 },
12331233 "shape": {"enum": ["circle", "ellipce", "rectangle", "rhombus", "triangle"]},
12341234 "figure": {
12351235 "properties": {
12361236 "name": {"type": "string"},
12371237 "shape": {"ref": "shape"},
12381238 "area": {"type": "float64"},
12391239 "center": {"ref": "point"}
12401240 }
12411241 }
12421242 },
12431243 "elements": {"ref": "figure"}
12441244}
12451245```
12461246
12471247The 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.
12481248
12491249Full source code of the test application:
12501250
12511251``` pascal
12521252program jtd_test;
12531253
12541254{$mode objfpc}{$H+}
12551255
12561256uses
12571257 Classes, SysUtils, fpJsonRtti, lgJson, lgJsonTypeDef, Figures;
12581258
12591259type
12601260{$PUSH}{$M+}
12611261 TMyPoint = class
12621262 private
12631263 FX,
12641264 FY: Integer;
12651265 published
12661266 property x: Integer read FX write FX;
12671267 property y: Integer read FY write FY;
12681268 end;
12691269{$POP}
12701270
12711271 TMyFigure = class(TCollectionItem)
12721272 private
12731273 FArea: Double;
12741274 FCenter: TMyPoint;
12751275 FName: string;
12761276 FShape: TShape;
12771277 public
12781278 constructor Create(ACollection: TCollection); override;
12791279 destructor Destroy; override;
12801280 published
12811281 property name: string read FName write FName;
12821282 property shape: TShape read FShape write FShape;
12831283 property area: Double read FArea write FArea;
12841284 property center: TMyPoint read FCenter;
12851285 end;
12861286
12871287constructor TMyFigure.Create(ACollection: TCollection);
12881288begin
12891289 inherited Create(ACollection);
12901290 FCenter := TMyPoint.Create;
12911291end;
12921292
12931293destructor TMyFigure.Destroy;
12941294begin
12951295 FCenter.Free;
12961296 inherited;
12971297end;
12981298
12991299var
13001300 s: string;
13011301
13021302procedure TestCollection;
13031303var
13041304 c: TCollection;
13051305 Start, Stop: QWord;
13061306begin
13071307 c := TCollection.Create(TMyFigure);
13081308 Start := GetTickCount64;
13091309 with TJSONDeStreamer.Create(nil) do
13101310 try
13111311 JSONToCollection(s, c);
13121312 finally
13131313 Free;
13141314 end;
13151315 Stop := GetTickCount64;
13161316 c.Free;
13171317 WriteLn('TJSONDeStreamer.JSONToCollection: ', Stop - Start);
13181318end;
13191319
13201320procedure TestJtd;
13211321var
13221322 Node: TJsonNode;
13231323 Schema: TJtdSchema;
13241324 eList: TJtdErrorList;
13251325 Figures: TFigureList;
13261326 Start, Stop: QWord;
13271327begin
13281328 Start := GetTickCount64;
13291329 if not TJsonNode.TryParse(s, Node) then begin
13301330 WriteLn('Failed to load data');
13311331 exit;
13321332 end;
13331333 Stop := GetTickCount64;
13341334 WriteLn('Loading data into TJsonNode: ', Stop - Start);
13351335
13361336 if not TJtdSchema.TryLoadFromFile('schema.json', Schema) then begin
13371337 WriteLn('Failed to load schema');
13381338 exit;
13391339 end;
13401340
13411341 Start := GetTickCount64;
13421342 if Validate(Node, Schema, eList) <> jvrOk then begin
13431343 WriteLn('Failed to validate data');
13441344 exit;
13451345 end;
13461346 Stop := GetTickCount64;
13471347 Schema.Free;
13481348 WriteLn('TJsonNode data validation: ', Stop - Start);
13491349
13501350 Start := GetTickCount64;
13511351 Figures := TFigureList.ReadJson(Node) as TFigureList;
13521352 Stop := GetTickCount64;
13531353 Figures.Free;
13541354 Node.Free;
13551355 WriteLn('Loadind JTD class from TJsonNode: ', Stop - Start);
13561356
13571357 Start := GetTickCount64;
13581358 Figures := TFigureList.ReadJson(s) as TFigureList;
13591359 Stop := GetTickCount64;
13601360 Figures.Free;
13611361 WriteLn('Direct loading of JTD class: ', Stop - Start);
13621362end;
13631363
13641364procedure LoadData;
13651365begin
13661366 with TStringStream.Create do
13671367 try
13681368 LoadFromFile('data.json');
13691369 s := DataString;
13701370 finally
13711371 Free;
13721372 end;
13731373end;
13741374
13751375begin
13761376 LoadData;
13771377
13781378 TestCollection;
13791379 TestJtd;
13801380
13811381end.
13821382```
13831383
13841384On my machine, it prints results like this:
13851385
13861386 TJSONDeStreamer.JSONToCollection: 5304
13871387 Loading data into TJsonNode: 1513
13881388 TJsonNode data validation: 1732
13891389 Loadind JTD class from TJsonNode: 515
13901390 Direct loading of JTD class: 1560
13911391
13921392Conclusion: the total validation and loading time of the JTD class is still noticeably better than the TCollection load time (3760 vs 5304).
13931393
13941394The direct load time of the JTD class is better than the load time of the TCollection by more than three times.
13951395
13961396---
13971397*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).*
13981398Category: JSON
13991399Category: Libraries

Back to history