Sign in Join
Wiki copytemporary

LGenerics: what changed

Revision 1 to 2. 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-<figure>
10901089<img src="sort_bench_1000000.png" width="800" />
1091-</figure>
10921090
10931091### Algorithms on graphs
10941092
10951093For 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).
10961094
10971095So, AGraph vs LGenerics:
10981096
10991097| problem | graph | AGraph time(ms) | LGenerics time(ms) | notes |
11001098|----|----|----|----|----|
11011099| BFS traversal | undirected, V=500000, E=4000000 | 640 | 109 | |
11021100| BFS traversal | directed, V=500000, E=4000000 | 484 | 94 | |
11031101| Connected components | undirected, V=500000, E=4000000 | 764 | 0 | If no edges were removed, TGSimpleGraph always knows the number and ownership of connected components |
11041102| Strongly connected components | directed, V=500000, E=4000000 | 1060 | 265 | |
11051103| Single-source shortest path | undirected, V=500000, E=4000000 | 3011 | 842 | |
11061104| Single-source shortest path | directed, V=500000, E=4000000 | 2153 | 718 | |
11071105| Single-pair shortest path | undirected, V=500000, E=4000000 | 2777 | 31 | |
11081106| Single-pair shortest path | directed, V=500000, E=4000000 | 1825 | 47 | |
11091107| Minimum spanning tree | undirected, V=500000, E=4000000 | 5974 | 640 | |
11101108| Maximum flow | dense, V=1000, E=499500 | 29499 | 47 | |
11111109| Maximum flow | sparse, V=64000, E=512000 | 3339 | 63 | |
11121110| Planarity test | undirected, V=219086, E=657252 | 1419 | 172 | |
11131111| Maximum clique | DIMACS brock200_2.clq, V=200, E=9876 | 84210 | 16 | |
11141112| Maximum clique | DIMACS brock200_4.clq, V=200, E=13089 | Cancelled(timeout 1 h) | 312 | |
11151113
11161114### Algorithms on strings/sequences
11171115
11181116It 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.
11191117
11201118It seems the C++ version only supports single-byte strings, so only compared to the single-byte version of SimRatioLevEx():
11211119
11221120 Dataset SimRatioLevEx() token_set_ratio()
11231121
11241122 Short/big_dist 1154 6440
11251123 Short/small_dist 967 3020
11261124 Medium/big_dist 811 3450
11271125 Medium/small_dist 702 1470
11281126 Long/big_dist 1966 15000
11291127 Long/small_dist 1061 2250
11301128
11311129The 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.
11321130
11331131The Go version, on the contrary, always works with Unicode strings:
11341132
11351133 Dataset SimRatioLevExUtf8() TokenSetRatio()
11361134
11371135 Short/big_dist 2143 18705
11381136 Short/small_dist 1593 2224
11391137 Medium/big_dist 1266 15062
11401138 Medium/small_dist 853 1742
11411139 Long/big_dist 3853 79851
11421140 Long/small_dist 1269 3126
11431141
11441142Go version: go1.10.4 linux/amd64; FPC-3.3.1-10683-g2a19e152b7 -O3. The benchmark was run on a virtual Linux machine.
11451143
11461144### Performance of JSON operations
11471145
11481146A 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.
11491147
11501148| description | FpJson | JsonTools | lgJson | notes |
11511149|----|----|----|----|----|
11521150| Average parsing speed on a set of JSON samples from the example/json/parse_bench folder, MB/s | 34,45 | 46,11 | 68,12 | |
11531151| 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 |
11541152| 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 | |
11551153| Search in a moderately sized document using path (750000 repeats), milliseconds | 8549 | 5398 | 873 | |
11561154| Dynamic creation of a small document and reading it as formatted JSON (150000 repeats), milliseconds | 7597 | 4150 | 1903 | |
11571155| Saving a moderate size document to a stream (10000 repeats), milliseconds | 4820 | 4587 | 827 | |
11581156| RFC 8259 compliance test, 295 test files from test/json_testset/testset | 5 failed | 23 failed | all passed | |
11591157
11601158#### Performance of export to JSON
11611159
11621160Datasets 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).
11631161
11641162TJSONStreamer.CollectionToJSON() was taken as a reference implementation (FpJsonStreamer for short), other members:
11651163
11661164- PdoToJson() with a collection as parameter (PdoCollection);
11671165- PdoToJson() with array as parameter and field name list registration (PdoRecordFieldMap);
11681166- PdoToJson() with array as parameter and custom callback registration (PdoRecordCallback);
11691167
11701168Each member exports the data several times (number of attempts is pre-defined), the best time is taken as the result.
11711169
11721170The full benchmark code can be found in the folder LGenerics/example/json/json_export_bench.
11731171
11741172Results on author's machine:
11751173
11761174 -------<JSON size 20 KB>-------
11771175 FpJsonStreamer......1,045 ms.
11781176 Rating: 22,23 MB/s.
11791177 PdoCollection.......0,123 ms.
11801178 Rating: 164,36 MB/s.
11811179 PdoRecordFieldMap...0,089 ms.
11821180 Rating: 226,76 MB/s.
11831181 PdoRecordCallback...0,057 ms.
11841182 Rating: 356,65 MB/s.
11851183 -------<JSON size 100 KB>-------
11861184 FpJsonStreamer......5,647 ms.
11871185 Rating: 20,51 MB/s.
11881186 PdoCollection.......0,626 ms.
11891187 Rating: 160,7 MB/s.
11901188 PdoRecordFieldMap...0,443 ms.
11911189 Rating: 226,72 MB/s.
11921190 PdoRecordCallback...0,285 ms.
11931191 Rating: 352,29 MB/s.
11941192 -------<JSON size 1 MB>-------
11951193 FpJsonStreamer......80,298 ms.
11961194 Rating: 14,34 MB/s.
11971195 PdoCollection.......6,552 ms.
11981196 Rating: 152,63 MB/s.
11991197 PdoRecordFieldMap...4,701 ms.
12001198 Rating: 212,71 MB/s.
12011199 PdoRecordCallback...3,107 ms.
12021200 Rating: 321,85 MB/s.
12031201 -------<JSON size 10 MB>-------
12041202 FpJsonStreamer......3554,483 ms.
12051203 Rating: 3,24 MB/s.
12061204 PdoCollection.......71,202 ms.
12071205 Rating: 140,57 MB/s.
12081206 PdoRecordFieldMap...53,309 ms.
12091207 Rating: 187,75 MB/s.
12101208 PdoRecordCallback...37,39 ms.
12111209 Rating: 267,69 MB/s.
12121210 -------<JSON size 50 MB>-------
12131211 FpJsonStreamer......84343,47 ms.
12141212 Rating: 0,68 MB/s.
12151213 PdoCollection.......350,224 ms.
12161214 Rating: 142,99 MB/s.
12171215 PdoRecordFieldMap...260,963 ms.
12181216 Rating: 191,89 MB/s.
12191217 PdoRecordCallback...183,328 ms.
12201218 Rating: 273,16 MB/s.
12211219
12221220#### Performance of JSON Type Definition
12231221
12241222Test schema:
12251223
12261224``` json
12271225{
12281226 "definitions": {
12291227 "point": {
12301228 "properties": {
12311229 "x": {"type": "int32"},
12321230 "y": {"type": "int32"}
12331231 }
12341232 },
12351233 "shape": {"enum": ["circle", "ellipce", "rectangle", "rhombus", "triangle"]},
12361234 "figure": {
12371235 "properties": {
12381236 "name": {"type": "string"},
12391237 "shape": {"ref": "shape"},
12401238 "area": {"type": "float64"},
12411239 "center": {"ref": "point"}
12421240 }
12431241 }
12441242 },
12451243 "elements": {"ref": "figure"}
12461244}
12471245```
12481246
12491247The 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.
12501248
12511249Full source code of the test application:
12521250
12531251``` pascal
12541252program jtd_test;
12551253
12561254{$mode objfpc}{$H+}
12571255
12581256uses
12591257 Classes, SysUtils, fpJsonRtti, lgJson, lgJsonTypeDef, Figures;
12601258
12611259type
12621260{$PUSH}{$M+}
12631261 TMyPoint = class
12641262 private
12651263 FX,
12661264 FY: Integer;
12671265 published
12681266 property x: Integer read FX write FX;
12691267 property y: Integer read FY write FY;
12701268 end;
12711269{$POP}
12721270
12731271 TMyFigure = class(TCollectionItem)
12741272 private
12751273 FArea: Double;
12761274 FCenter: TMyPoint;
12771275 FName: string;
12781276 FShape: TShape;
12791277 public
12801278 constructor Create(ACollection: TCollection); override;
12811279 destructor Destroy; override;
12821280 published
12831281 property name: string read FName write FName;
12841282 property shape: TShape read FShape write FShape;
12851283 property area: Double read FArea write FArea;
12861284 property center: TMyPoint read FCenter;
12871285 end;
12881286
12891287constructor TMyFigure.Create(ACollection: TCollection);
12901288begin
12911289 inherited Create(ACollection);
12921290 FCenter := TMyPoint.Create;
12931291end;
12941292
12951293destructor TMyFigure.Destroy;
12961294begin
12971295 FCenter.Free;
12981296 inherited;
12991297end;
13001298
13011299var
13021300 s: string;
13031301
13041302procedure TestCollection;
13051303var
13061304 c: TCollection;
13071305 Start, Stop: QWord;
13081306begin
13091307 c := TCollection.Create(TMyFigure);
13101308 Start := GetTickCount64;
13111309 with TJSONDeStreamer.Create(nil) do
13121310 try
13131311 JSONToCollection(s, c);
13141312 finally
13151313 Free;
13161314 end;
13171315 Stop := GetTickCount64;
13181316 c.Free;
13191317 WriteLn('TJSONDeStreamer.JSONToCollection: ', Stop - Start);
13201318end;
13211319
13221320procedure TestJtd;
13231321var
13241322 Node: TJsonNode;
13251323 Schema: TJtdSchema;
13261324 eList: TJtdErrorList;
13271325 Figures: TFigureList;
13281326 Start, Stop: QWord;
13291327begin
13301328 Start := GetTickCount64;
13311329 if not TJsonNode.TryParse(s, Node) then begin
13321330 WriteLn('Failed to load data');
13331331 exit;
13341332 end;
13351333 Stop := GetTickCount64;
13361334 WriteLn('Loading data into TJsonNode: ', Stop - Start);
13371335
13381336 if not TJtdSchema.TryLoadFromFile('schema.json', Schema) then begin
13391337 WriteLn('Failed to load schema');
13401338 exit;
13411339 end;
13421340
13431341 Start := GetTickCount64;
13441342 if Validate(Node, Schema, eList) <> jvrOk then begin
13451343 WriteLn('Failed to validate data');
13461344 exit;
13471345 end;
13481346 Stop := GetTickCount64;
13491347 Schema.Free;
13501348 WriteLn('TJsonNode data validation: ', Stop - Start);
13511349
13521350 Start := GetTickCount64;
13531351 Figures := TFigureList.ReadJson(Node) as TFigureList;
13541352 Stop := GetTickCount64;
13551353 Figures.Free;
13561354 Node.Free;
13571355 WriteLn('Loadind JTD class from TJsonNode: ', Stop - Start);
13581356
13591357 Start := GetTickCount64;
13601358 Figures := TFigureList.ReadJson(s) as TFigureList;
13611359 Stop := GetTickCount64;
13621360 Figures.Free;
13631361 WriteLn('Direct loading of JTD class: ', Stop - Start);
13641362end;
13651363
13661364procedure LoadData;
13671365begin
13681366 with TStringStream.Create do
13691367 try
13701368 LoadFromFile('data.json');
13711369 s := DataString;
13721370 finally
13731371 Free;
13741372 end;
13751373end;
13761374
13771375begin
13781376 LoadData;
13791377
13801378 TestCollection;
13811379 TestJtd;
13821380
13831381end.
13841382```
13851383
13861384On my machine, it prints results like this:
13871385
13881386 TJSONDeStreamer.JSONToCollection: 5304
13891387 Loading data into TJsonNode: 1513
13901388 TJsonNode data validation: 1732
13911389 Loadind JTD class from TJsonNode: 515
13921390 Direct loading of JTD class: 1560
13931391
13941392Conclusion: the total validation and loading time of the JTD class is still noticeably better than the TCollection load time (3760 vs 5304).
13951393
13961394The direct load time of the JTD class is better than the load time of the TCollection by more than three times.
13971395
13981396---
13991397*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).*
14001398Category: JSON
14011399Category: Libraries

Back to history