Recent

Author Topic: LGenerics: support for JSON Type Definition  (Read 7829 times)

totya

  • Hero Member
  • *****
  • Posts: 719
Re: LGenerics: support for JSON Type Definition
« Reply #75 on: March 05, 2023, 05:58:05 pm »
Hi!

I just report an error.

I'd like to generate code schema from file, but I got an exception: "Cannot parse json string".

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   Schema: TJtdSchema;
  4.   PasCodegen: TJtdPasCodegen;
  5. begin
  6.   Schema := TJtdSchema.Create;
  7.   try
  8.     Schema.Load('SampleSchema.json');
  9.  
  10.    // Process code later...
  11.  
  12.   finally
  13.     Schema.Free;
  14.   end;
  15. end;

I'm posting it here again because the previous message is spam.

Edit.: Okay, Need: LoadFromFile and not "Load".  O:-)
« Last Edit: March 05, 2023, 06:13:23 pm by totya »

totya

  • Hero Member
  • *****
  • Posts: 719
Re: LGenerics: support for JSON Type Definition
« Reply #76 on: March 05, 2023, 07:43:45 pm »
Hi, I have a scheme:

Code: Pascal  [Select][+][-]
  1. {
  2.   "properties": {
  3.     "Index": {
  4.       "type": "int32"
  5.     },
  6.     "Value1": {
  7.       "type": "string"
  8.     },
  9.     "Value2": {
  10.       "type": "string"
  11.     },
  12.     "Value3": {
  13.       "type": "string"
  14.     },
  15.     "Value4": {
  16.       "type": "string"
  17.     }
  18.   }
  19. }
  20.  

Generated code:

Code: Pascal  [Select][+][-]
  1. {
  2.   This unit was automatically created by JtdPasCodegen, do not edit.
  3. }
  4. unit unit_Json_Sample;
  5.  
  6. {$MODE OBJFPC}{$H+}{$B-}
  7.  
  8. interface
  9.  
  10. uses
  11.   SysUtils, lgJson, lgJtdTypes;
  12.  
  13. type
  14.  
  15.   TJsonSample = class sealed(TJtdObject)
  16.   private
  17.     FIndex: TJtdInt32;
  18.     FValue1: TJtdString;
  19.     FValue2: TJtdString;
  20.     FValue3: TJtdString;
  21.     FValue4: TJtdString;
  22.     procedure SetIndex(aValue: TJtdInt32);
  23.     procedure SetValue1(aValue: TJtdString);
  24.     procedure SetValue2(aValue: TJtdString);
  25.     procedure SetValue3(aValue: TJtdString);
  26.     procedure SetValue4(aValue: TJtdString);
  27.   protected
  28.     procedure DoReadJson(aNode: TJsonNode); override;
  29.     procedure DoReadJson(aReader: TJsonReader); override;
  30.     procedure DoWriteJson(aWriter: TJsonStrWriter); override;
  31.   public
  32.     class function GetJtdClass: TJtdEntityClass; override;
  33.     procedure Clear; override;
  34.   { refers to "Index" JSON property }
  35.     property Index: TJtdInt32 read FIndex write SetIndex;
  36.   { refers to "Value1" JSON property }
  37.     property Value1: TJtdString read FValue1 write SetValue1;
  38.   { refers to "Value2" JSON property }
  39.     property Value2: TJtdString read FValue2 write SetValue2;
  40.   { refers to "Value3" JSON property }
  41.     property Value3: TJtdString read FValue3 write SetValue3;
  42.   { refers to "Value4" JSON property }
  43.     property Value4: TJtdString read FValue4 write SetValue4;
  44.   end;
  45.  
  46. implementation
  47.  
  48. { TJsonSample }
  49.  
  50. class function TJsonSample.GetJtdClass: TJtdEntityClass;
  51. begin
  52.   Result := TJsonSample;
  53. end;
  54.  
  55. procedure TJsonSample.Clear;
  56. begin
  57.   FreeAndNil(FIndex);
  58.   FreeAndNil(FValue1);
  59.   FreeAndNil(FValue2);
  60.   FreeAndNil(FValue3);
  61.   FreeAndNil(FValue4);
  62. end;
  63.  
  64. procedure TJsonSample.SetIndex(aValue: TJtdInt32);
  65. begin
  66.   if aValue = FIndex then exit;
  67.   FIndex.Free;
  68.   FIndex := aValue;
  69. end;
  70.  
  71. procedure TJsonSample.SetValue1(aValue: TJtdString);
  72. begin
  73.   if aValue = FValue1 then exit;
  74.   FValue1.Free;
  75.   FValue1 := aValue;
  76. end;
  77.  
  78. procedure TJsonSample.SetValue2(aValue: TJtdString);
  79. begin
  80.   if aValue = FValue2 then exit;
  81.   FValue2.Free;
  82.   FValue2 := aValue;
  83. end;
  84.  
  85. procedure TJsonSample.SetValue3(aValue: TJtdString);
  86. begin
  87.   if aValue = FValue3 then exit;
  88.   FValue3.Free;
  89.   FValue3 := aValue;
  90. end;
  91.  
  92. procedure TJsonSample.SetValue4(aValue: TJtdString);
  93. begin
  94.   if aValue = FValue4 then exit;
  95.   FValue4.Free;
  96.   FValue4 := aValue;
  97. end;
  98.  
  99. {$PUSH}{$WARN 5057 OFF}
  100. procedure TJsonSample.DoReadJson(aNode: TJsonNode);
  101. var
  102.   p: TJsonNode.TPair;
  103.   Flags: array[0..4] of Boolean;
  104.   I: Integer;
  105. begin
  106.   if not aNode.IsObject then ReadError;
  107.   Clear;
  108.   System.FillChar(Flags, SizeOf(Flags), 0);
  109.   for p in aNode.Entries do
  110.     case p.Key of
  111.       'Index':
  112.         begin
  113.           FIndex := TJtdInt32(TJtdInt32.ReadJson(p.Value));
  114.           Flags[0] := True;
  115.         end;
  116.       'Value1':
  117.         begin
  118.           FValue1 := TJtdString(TJtdString.ReadJson(p.Value));
  119.           Flags[1] := True;
  120.         end;
  121.       'Value2':
  122.         begin
  123.           FValue2 := TJtdString(TJtdString.ReadJson(p.Value));
  124.           Flags[2] := True;
  125.         end;
  126.       'Value3':
  127.         begin
  128.           FValue3 := TJtdString(TJtdString.ReadJson(p.Value));
  129.           Flags[3] := True;
  130.         end;
  131.       'Value4':
  132.         begin
  133.           FValue4 := TJtdString(TJtdString.ReadJson(p.Value));
  134.           Flags[4] := True;
  135.         end;
  136.     else
  137.       UnknownProp(p.Key);
  138.     end;
  139.   for I := 0 to System.High(Flags) do
  140.     if not Flags[I] then
  141.       case I of
  142.         0: PropNotFound('Index');
  143.         1: PropNotFound('Value1');
  144.         2: PropNotFound('Value2');
  145.         3: PropNotFound('Value3');
  146.         4: PropNotFound('Value4');
  147.       else
  148.       end;
  149. end;
  150. {$POP}
  151.  
  152. {$PUSH}{$WARN 5057 OFF}
  153. procedure TJsonSample.DoReadJson(aReader: TJsonReader);
  154. var
  155.   Flags: array[0..4] of Boolean;
  156.   I: Integer;
  157. begin
  158.   if aReader.TokenKind <> tkObjectBegin then ReadError;
  159.   Clear;
  160.   System.FillChar(Flags, SizeOf(Flags), 0);
  161.   repeat
  162.     if not aReader.Read then ReadError;
  163.     if aReader.TokenKind = tkObjectEnd then break;
  164.     case aReader.Name of
  165.       'Index':
  166.         begin
  167.           FIndex := TJtdInt32(TJtdInt32.ReadJson(aReader));
  168.           Flags[0] := True;
  169.         end;
  170.       'Value1':
  171.         begin
  172.           FValue1 := TJtdString(TJtdString.ReadJson(aReader));
  173.           Flags[1] := True;
  174.         end;
  175.       'Value2':
  176.         begin
  177.           FValue2 := TJtdString(TJtdString.ReadJson(aReader));
  178.           Flags[2] := True;
  179.         end;
  180.       'Value3':
  181.         begin
  182.           FValue3 := TJtdString(TJtdString.ReadJson(aReader));
  183.           Flags[3] := True;
  184.         end;
  185.       'Value4':
  186.         begin
  187.           FValue4 := TJtdString(TJtdString.ReadJson(aReader));
  188.           Flags[4] := True;
  189.         end;
  190.     else
  191.       UnknownProp(aReader.Name);
  192.     end;
  193.   until False;
  194.   for I := 0 to System.High(Flags) do
  195.     if not Flags[I] then
  196.       case I of
  197.         0: PropNotFound('Index');
  198.         1: PropNotFound('Value1');
  199.         2: PropNotFound('Value2');
  200.         3: PropNotFound('Value3');
  201.         4: PropNotFound('Value4');
  202.       else
  203.       end;
  204. end;
  205. {$POP}
  206.  
  207. procedure TJsonSample.DoWriteJson(aWriter: TJsonStrWriter);
  208. begin
  209.   aWriter.BeginObject;
  210.   aWriter.AddName('Index');
  211.   Index.WriteJson(aWriter);
  212.   aWriter.AddName('Value1');
  213.   Value1.WriteJson(aWriter);
  214.   aWriter.AddName('Value2');
  215.   Value2.WriteJson(aWriter);
  216.   aWriter.AddName('Value3');
  217.   Value3.WriteJson(aWriter);
  218.   aWriter.AddName('Value4');
  219.   Value4.WriteJson(aWriter);
  220.   aWriter.EndObject;
  221. end;
  222.  
  223. end.
  224.  

The question is same as before, because its a wonderful big library, just lack of the simple usage examples.

So I'd like to ask, I got the TJsonSample class, and how can I use it?
I guess TJtdMap-> TJtdMapEntry, then TJtdObject -> TJtdObjectEntry? There is no such thing with this name.

- Set value? Perhaps for example JsonSample.Index.Create(IndexValue);
- Add value? (new value!)
- Save items to json file?
- Enumerate items?
- Access items?


Thank you!

avk

  • Hero Member
  • *****
  • Posts: 735
Re: LGenerics: support for JSON Type Definition
« Reply #77 on: March 06, 2023, 10:16:27 am »
The purpose of all these classes(which TJtdPasCodegen generates) is to provide a regular structure whose properties and methods are all known at compile time, and into which a JSON instance can be safely deserialized, as provided that it satisfies the specified schema.
Also, these classes can serialize themselves back to JSON, but the result of the serialization usually needs to be validated against the schema.

The whole process could look something like this (JSON match your schema):
Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   SysUtils, lgUtils, lgJsonTypeDef, lgJson, unit_Json_Sample;
  7.  
  8. var
  9.   JsonRef: specialize TGUniqRef<TJsonNode>;
  10.   SchemaRef: specialize TGUniqRef<TJtdSchema>;
  11.   SampleRef: specialize TGUniqRef<TJsonSample>;
  12.   MyJson: TJsonNode = nil;
  13.   MySchema: TJtdSchema = nil;
  14.   ErrList: TJtdErrorList;
  15.   MySample: TJsonSample = nil;
  16.  
  17. const
  18.   Schema =
  19.     '{"properties":{"Index":{"type":"int32"},"Value1":{"type": "string"},"Value2":{"type":"string"},' +
  20.     '"Value3":{"type":"string"},"Value4":{"type": "string"}}}';
  21.  
  22.   Json = '{"Index":42,"Value1":"aa","Value2":"bb","Value3":"cc","Value4":"dd"}';
  23.  
  24. begin
  25.   if TJsonNode.TryParse(Json, MyJson) then begin
  26.     JsonRef.Instance := MyJson;
  27.     //of course, the schema can be loaded in advance and used more than once
  28.     if TJtdSchema.TryLoad(Schema, MySchema) then begin
  29.       SchemaRef.Instance := MySchema;
  30.       if Validate(MyJson, MySchema, ErrList) = jvrOk then begin
  31.         SampleRef.Instance := TJsonSample.ReadJson(MyJson) as TJsonSample;
  32.         MySample := SampleRef;
  33.         WriteLn('MySample.Index: ', MySample.Index.Value);
  34.         WriteLn('MySample.Value1: ', MySample.Value1.Value);
  35.         WriteLn('MySample.Value2: ', MySample.Value2.Value);
  36.         WriteLn('MySample.Value3: ', MySample.Value3.Value);
  37.         WriteLn('MySample.Value4: ', MySample.Value4.Value);
  38.         //let's try changing some of the properties
  39.         MySample.Index.Value := 1001;
  40.         MySample.Value4.Value := 'some value';
  41.         //let's try to serialize it to JSON
  42.         WriteLn('MySample.AsJson: ', MySample.AsJson);
  43.       end else
  44.         WriteLn('JSON instance does not match the schema');
  45.     end else
  46.       WriteLn('Invalid schema instance');
  47.   end else
  48.     WriteLn('Invalid JSON instance');
  49.   ReadLn;
  50. end.
  51.  

But if you are absolutely sure that the JSON you get has the desired structure, things can be a bit simpler:
Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   SysUtils, lgUtils, unit_Json_Sample;
  7.  
  8. var
  9.   SampleRef: specialize TGUniqRef<TJsonSample>;
  10.   MySample: TJsonSample = nil;
  11.  
  12. const
  13.   Json = '{"Index":42,"Value1":"aa","Value2":"bb","Value3":"cc","Value4":"dd"}';
  14.  
  15. begin
  16.   SampleRef.Instance := TJsonSample.ReadJson(Json) as TJsonSample;
  17.   MySample := SampleRef;
  18.   WriteLn('MySample.Index: ', MySample.Index.Value);
  19.   WriteLn('MySample.Value1: ', MySample.Value1.Value);
  20.   WriteLn('MySample.Value2: ', MySample.Value2.Value);
  21.   WriteLn('MySample.Value3: ', MySample.Value3.Value);
  22.   WriteLn('MySample.Value4: ', MySample.Value4.Value);
  23.   //let's try changing some of the properties
  24.   MySample.Index.Value := 1001;
  25.   MySample.Value4.Value := 'some value';
  26.   //let's try to serialize it to JSON
  27.   WriteLn('MySample.AsJson: ', MySample.AsJson);
  28.   ReadLn;
  29. end.
  30.  

AlexTP

  • Hero Member
  • *****
  • Posts: 2118
    • UVviewsoft
Re: LGenerics: support for JSON Type Definition
« Reply #78 on: March 06, 2023, 10:20:25 am »
@avk, I see new good examples. Then let's write them to wiki https://wiki.freepascal.org/LGenerics#Support_for_JSON_Type_Definition ? If wiki misses this or similar examples.

totya

  • Hero Member
  • *****
  • Posts: 719
Re: LGenerics: support for JSON Type Definition
« Reply #79 on: March 06, 2023, 04:33:20 pm »

The question is same as before, because its a wonderful big library, but completely useless now.

- Set value?
- Add value? (new value!)
- Save items to json file?
- Enumerate items?
- Access items?


Thank you!

Thaddy

  • Hero Member
  • *****
  • Posts: 12973
Re: LGenerics: support for JSON Type Definition
« Reply #80 on: March 06, 2023, 04:37:43 pm »
Useless?
Completely the opposite and well written using proper scientific best practice.
I actually get compliments for being rude... (well, Dutch, but that is the same)

AlexTP

  • Hero Member
  • *****
  • Posts: 2118
    • UVviewsoft
Re: LGenerics: support for JSON Type Definition
« Reply #81 on: March 06, 2023, 04:38:33 pm »
Please answer @totya's question in the wiki as well.

totya

  • Hero Member
  • *****
  • Posts: 719
Re: LGenerics: support for JSON Type Definition
« Reply #82 on: March 06, 2023, 04:39:16 pm »
Useless?
Completely the opposite and well written using proper scientific best practice.

Please stop the constant spamming, if you are bored, go somewhere else!

Thaddy

  • Hero Member
  • *****
  • Posts: 12973
Re: LGenerics: support for JSON Type Definition
« Reply #83 on: March 06, 2023, 04:42:15 pm »
Please answer @totya's question in the wiki as well.
I will try to this week.

Also note avg is not shorthand for average. Totya should pay attention, he is plain rude and expects that other people solve his problems.
I actually get compliments for being rude... (well, Dutch, but that is the same)

avk

  • Hero Member
  • *****
  • Posts: 735
Re: LGenerics: support for JSON Type Definition
« Reply #84 on: March 06, 2023, 06:58:03 pm »
The question is same as before, because its a wonderful big library, but completely useless now.

Without a doubt, if you do not know what you can use something for, then this something is completely useless for you. JTD is needed for about the same purposes as DTD or XML Schema, but for JSON. If these names do not mean anything to you, then most likely you do not need a JTD at all.
Of course, you know better, but if you are interested, then it makes sense to look into the JTD documentation.

- Set value?

Does the example not set the property values?

- Add value? (new value!)

If you mean a new property, then your schema doesn't allow it.

- Save items to json file?

Would you like to add a SaveToFile() method? It certainly can be done, but is it necessary?

- Enumerate items?

Do you mean enumerate class properties?

- Access items?

WTF?

avk

  • Hero Member
  • *****
  • Posts: 735
Re: LGenerics: support for JSON Type Definition
« Reply #85 on: March 08, 2023, 05:28:49 pm »
Please answer @totya's question in the wiki as well.

Sorry, didn't see your post in time.
But maybe you have a good way how it could be done?

avk

  • Hero Member
  • *****
  • Posts: 735
Re: LGenerics: support for JSON Type Definition
« Reply #86 on: March 18, 2023, 09:36:48 am »
Hi!

JTD implementation of LGenerics has finally added a utility that can generate a JSON Typedef schema from example data (unit lgJtdInfer).
This simple program:
Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   SysUtils, lgUtils, lgJson, lgJtdInfer;
  7.  
  8. var
  9.   Schema: specialize TGUniqRef<TJsonNode>;
  10.  
  11. const
  12.   Samples: array of string = (
  13.     '{"id":"123","kind":"LEGACY","status":"OK","tags":{"foo":"bar"}}',
  14.     '{"id":"456","kind":"LEGACY","status":"ERROR","tags":{"baz":"quux"}}',
  15.     '{"id":"789","kind":"MODERN","status":"OK","tags":{"bar": "foo" }}'
  16.   );
  17. begin
  18.   Schema.Instance := TJtdInferrer.Infer(Samples, []);
  19.   WriteLn(Schema.Instance.FormatJson(DefaultJsonFmtStyle));
  20. end.
  21.  

will generate a schema like this:
Code: Javascript  [Select][+][-]
  1. {
  2.     "properties": {
  3.         "id": {"type": "string"},
  4.         "kind": {"type": "string"},
  5.         "status": {"type": "string"},
  6.         "tags": {
  7.             "optionalProperties": {
  8.                 "foo": {"type": "string"},
  9.                 "baz": {"type": "string"},
  10.                 "bar": {"type": "string"}
  11.             }
  12.         }
  13.     }
  14. }
  15.  

Inference algorithm itself never outputs Enum, Values, or Discriminator schemas until it gets appropriate hint. If you think it might improve the result, just give it such a hint.
For example, you can assume that kind and status fields should be treated as enumeration and tags as a dictionary:
Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   SysUtils, lgUtils, lgJson, lgJtdInfer;
  7.  
  8. type
  9.   THint = TJtdInferrer.THint;
  10.  
  11. var
  12.   Schema: specialize TGUniqRef<TJsonNode>;
  13.  
  14. const
  15.   Samples: array of string = (
  16.     '{"id":"123","kind":"LEGACY","status":"OK","tags":{"foo":"bar"}}',
  17.     '{"id":"456","kind":"LEGACY","status":"ERROR","tags":{"baz":"quux"}}',
  18.     '{"id":"789","kind":"MODERN","status":"OK","tags":{"bar": "foo" }}'
  19.   );
  20. begin
  21.   Schema.Instance := TJtdInferrer.Infer(
  22.     Samples, [THint.Enum(['kind']), THint.Enum(['status']), THint.Map(['tags'])]);
  23.   WriteLn(Schema.Instance.FormatJson(DefaultJsonFmtStyle));
  24. end.
  25.  

which will result in:
Code: Javascript  [Select][+][-]
  1. {
  2.     "properties": {
  3.         "id": {"type": "string"},
  4.         "kind": {
  5.             "enum": ["LEGACY", "MODERN"]
  6.         },
  7.         "status": {
  8.             "enum": ["OK", "ERROR"]
  9.         },
  10.         "tags": {
  11.             "values": {"type": "string"}
  12.         }
  13.     }
  14. }
  15.  

Happy coding!

avra

  • Hero Member
  • *****
  • Posts: 2457
    • Additional info
Re: LGenerics: support for JSON Type Definition
« Reply #87 on: March 18, 2023, 11:56:55 am »
JTD implementation of LGenerics has finally added a utility that can generate a JSON Typedef schema from example data
Thanks a lot!

JSON schema generation and data validation will be really useful  :D 8-) :D

Here is an online validation check pass for your sample data and generated schema:
https://www.jsonschemavalidator.net/s/3bt1shff
« Last Edit: March 18, 2023, 12:11:09 pm by avra »
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

avk

  • Hero Member
  • *****
  • Posts: 735
Re: LGenerics: support for JSON Type Definition
« Reply #88 on: March 18, 2023, 01:15:10 pm »
Just in case, to avoid ambiguity: JSON Schema is a completely different project, though related. Much more ambitious and sophisticated, while JTD is simple and practical.

Thaddy

  • Hero Member
  • *****
  • Posts: 12973
Re: LGenerics: support for JSON Type Definition
« Reply #89 on: March 18, 2023, 03:53:48 pm »
Here I beg to differ: Json is easy and JTD is more complex.
I actually get compliments for being rude... (well, Dutch, but that is the same)

 

TinyPortal © 2005-2018