Difference between revisions of "fcl-json"

From Free Pascal wiki
Jump to navigationJump to search
 
(29 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
{{fcl-json}}
 
{{fcl-json}}
  
= Info =
+
= General info =
  
 
fcl-json is a [[JSON]] implementation.  
 
fcl-json is a [[JSON]] implementation.  
Line 12: Line 12:
 
* jsonScanner: json source lexical analyzer
 
* jsonScanner: json source lexical analyzer
  
Note: In fpjson, accessing e.g. SomeJSONObject.Integers['price'] may give a SIGSEGV/Access Violation if that integer variable does not exist. This is apparently intentional, see [http://bugs.freepascal.org/view.php?id=22273].  
+
Note: In fpjson, accessing e.g.
 +
<syntaxhighlight lang="pascal">
 +
SomeJSONObject.Integers['price']
 +
</syntaxhighlight>
 +
may give a SIGSEGV/Access Violation if that integer variable does not exist. This is apparently intentional, see [http://bugs.freepascal.org/view.php?id=22273].  
 
You'd have to use the Find method (available since FPC 2.6.2) to first check if the element ('price' in this example) exists.
 
You'd have to use the Find method (available since FPC 2.6.2) to first check if the element ('price' in this example) exists.
  
Line 26: Line 30:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
 
 
uses
 
uses
 
   fpjson, jsonparser;
 
   fpjson, jsonparser;
Line 37: Line 40:
 
   s : String;
 
   s : String;
 
begin
 
begin
 
 
 
   // this is only a minimal sampling of what can be done with this API
 
   // this is only a minimal sampling of what can be done with this API
  
Line 50: Line 52:
  
 
   // cast as TJSONObject to make access easier
 
   // cast as TJSONObject to make access easier
   jObject := TJSONObject(jData);
+
   jObject := jData as TJSONObject;
  
 
   // retrieve value of Fld1
 
   // retrieve value of Fld1
Line 75: Line 77:
  
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
Note: it's necessary to free jData when you have finished with it. Otherwise a memory leak is created.
  
 
== Traversing Items ==
 
== Traversing Items ==
Line 114: Line 118:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Save/load dialog position/size ==
+
== Save/load form position to/from file==
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 120: Line 124:
 
   jsonConf;
 
   jsonConf;
  
procedure TfmMain.SaveOptionsPos;
+
procedure SaveFormPos(AForm: TForm; const AFilename: string);
 
var
 
var
 
   c: TJSONConfig;
 
   c: TJSONConfig;
Line 126: Line 130:
 
   c:= TJSONConfig.Create(Nil);
 
   c:= TJSONConfig.Create(Nil);
 
   try
 
   try
     c.Filename:= GetAppPath(cFileHistory);
+
     //try/except to handle broken json file
     c.SetValue('/dialog/max', WindowState=wsMaximized);
+
    try
     if WindowState<>wsMaximized then
+
      c.Formatted:= true;
 +
      c.Filename:= AFilename;
 +
    except
 +
      exit;
 +
    end;
 +
 
 +
     c.SetValue('/dialog/max', AForm.WindowState=wsMaximized);
 +
     if AForm.WindowState<>wsMaximized then
 
     begin
 
     begin
       c.SetValue('/dialog/posx', Left);
+
       c.SetValue('/dialog/posx', AForm.Left);
       c.SetValue('/dialog/posy', Top);
+
       c.SetValue('/dialog/posy', AForm.Top);
       c.SetValue('/dialog/sizex', Width);
+
       c.SetValue('/dialog/sizex', AForm.Width);
       c.SetValue('/dialog/sizey', Height);
+
       c.SetValue('/dialog/sizey', AForm.Height);
 
     end;
 
     end;
 
   finally
 
   finally
Line 140: Line 151:
 
end;
 
end;
  
procedure TfmMain.LoadOptionsPos;
+
procedure LoadFormPos(AForm: TForm; const AFilename: string);
 
var
 
var
 
   nLeft, nTop, nW, nH: Integer;
 
   nLeft, nTop, nW, nH: Integer;
Line 147: Line 158:
 
   c:= TJSONConfig.Create(Nil);
 
   c:= TJSONConfig.Create(Nil);
 
   try
 
   try
     c.Filename:= GetAppPath(cFileHistory);
+
     //try/except to handle broken json file
 +
    try
 +
      c.Formatted:= true;
 +
      c.Filename:= AFilename;
 +
    except
 +
      exit;
 +
    end;
  
     nLeft:= c.GetValue('/dialog/posx', Left);
+
     nLeft:= c.GetValue('/dialog/posx', AForm.Left);
     nTop:= c.GetValue('/dialog/posy', Top);
+
     nTop:= c.GetValue('/dialog/posy', AForm.Top);
     nW:= c.GetValue('/dialog/sizex', Width);
+
     nW:= c.GetValue('/dialog/sizex', AForm.Width);
     nH:= c.GetValue('/dialog/sizey', Height);
+
     nH:= c.GetValue('/dialog/sizey', AForm.Height);
     SetBounds(nLeft, nTop, nW, nH);
+
     AForm.SetBounds(nLeft, nTop, nW, nH);
  
 
     if c.GetValue('/dialog/max', false) then
 
     if c.GetValue('/dialog/max', false) then
       WindowState:= wsMaximized;
+
       AForm.WindowState:= wsMaximized;
 
   finally
 
   finally
 
     c.Free;
 
     c.Free;
Line 166: Line 183:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
//Example of path: '/list_find'
+
uses
 
+
  jsonConf;
procedure SLoadStringsFromFile(cfg: TJsonConfig; const path: String; List: TStrings);
 
 
var
 
var
   i: Integer;
+
   cfg: TJSONConfig;
   s: UnicodeString;
+
   List: TStringList;
 +
  path: string;
 
begin
 
begin
   List.Clear;
+
   List:= TStringList.Create;
   for i:= 0 to OptMaxHistoryItems-1 do
+
   cfg:= TJSONConfig.Create(nil);
   begin
+
   try
     s:= cfg.GetValue(path+'/'+inttostr(i), '');
+
    //try/except to handle broken json file
     if s='' then
+
     try
       Break;
+
      cfg.Formatted:= true;
     List.Add(Utf8Encode(s));
+
      cfg.Filename:= AJsonFilename;
  end;
+
     except
end;
+
       exit;
 +
     end;
  
procedure SSaveStringsToFile(cfg: TJsonConfig; const path: String; List: TStrings);
+
    cfg.GetValue('/mylist', List, '');
var
+
     List.Add('some_value');
  i: Integer;
+
     cfg.SetValue('/mylist', List);
  s: String;
+
   finally
begin
+
    cfg.Free;
  for i:= 0 to OptMaxHistoryItems-1 do
+
    List.Free;
  begin
+
  end;
     if i<List.Count then
 
      s:= List[i]
 
    else
 
      s:= '';
 
     cfg.SetDeleteValue(path+'/'+inttostr(i), Utf8Decode(s), '');
 
   end;
 
end;
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 306: Line 317:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== FpcTwit ==
+
==Change formatting of float numbers==
 +
 
 +
Q: My program generates and writes data to JSON file. I use FormatJSON() method to make output more readable. I'm not quite satisfied how numbers with float point look:
 +
 
 +
"coordinates" : [
 +
      5.5978631048365003E+001,
 +
      2.2100000000000000E+002
 +
]
 +
 
 +
I want to see normal form:
  
The [[Components_and_Code_examples#Networking|Components and Code examples]] library makes use of JSON to send/receive data.
+
"coordinates" : [
 +
      55.978631048365003,
 +
      221.0
 +
]
 +
 
 +
A (forum members '''y.ivanov''', '''rvk''', '''wp'''):
 +
<syntaxhighlight lang="pascal">
 +
{$mode objfpc}{$h+}
 +
uses
 +
  fpjson,
 +
  jsonparser,
 +
  SysUtils;
 +
 +
type
 +
  TJSONFloat4Number = class(TJSONFloatNumber)
 +
  protected
 +
    function GetAsString: TJSONStringType; override;
 +
  end;
 +
 +
function TJSONFloat4Number.GetAsString: TJSONStringType;
 +
var
 +
  F: TJSONFloat;
 +
  fs: TFormatSettings;
 +
begin
 +
  fs := DefaultFormatSettings;
 +
  fs.DecimalSeparator := '.';
 +
  F := GetAsFloat;
 +
  Result := FormatFloat('0.0###############', F, fs); // format with your preferences
 +
end;
 +
 
 +
procedure JSONTest;
 +
var
 +
  jData: TJSONData;
 +
begin
 +
  jData := GetJSON('{"coordinates": [5.5978631048365003E+001, 2.2100000000000000E+002]}');
 +
  writeln(jData.FormatJSON);
 +
  jData.Free;
 +
end;
 +
 +
begin
 +
  SetJSONInstanceType(jitNumberFloat, TJSONFloat4Number);
 +
  JSONTest;
 +
  Readln;
 +
end.
 +
</syntaxhighlight>
  
 
== See also ==
 
== See also ==
  
* An article covering use of XML and JSON in FreePascal: [http://www.freepascal.org/~michael/articles/webdata/webdata.pdf PDF]
+
* The FpcTwit example in the [[Components_and_Code_examples#Networking|Components and Code examples]] library makes use of JSON to send/receive data.
 +
* [https://lazarus-ccr.sourceforge.io/fpcdoc/fcl/fpjson/index.html FCL Reference] for unit fpjson.
 +
* [http://www.freepascal.org/~michael/articles/webdata/webdata.pdf An article (PDF)] covering use of XML and JSON in FreePascal.
 
* [[Package List]]
 
* [[Package List]]

Latest revision as of 19:09, 6 October 2022

English (en) polski (pl) русский (ru) 中文(中国大陆)‎ (zh_CN)

General info

fcl-json is a JSON implementation.

It contains units such as:

  • fpjson: base unit which implements TJsonData and its children, e.g. TJsonObject
  • jsonParser: implements TJsonParser, used in the From JsonViewer example below
  • jsonConf: implements TJsonConfig which is handy to read/write application data from/to files
  • jsonScanner: json source lexical analyzer

Note: In fpjson, accessing e.g.

SomeJSONObject.Integers['price']

may give a SIGSEGV/Access Violation if that integer variable does not exist. This is apparently intentional, see [1]. You'd have to use the Find method (available since FPC 2.6.2) to first check if the element ('price' in this example) exists.

Streaming

fcl-json contains the unit "fpjsonrtti" which is used to load objects (TObject instances) from or save them to JSON format.

See Streaming JSON for a short example.

Examples

Getting Started

uses
  fpjson, jsonparser;

procedure JSONTest;
var
  jData : TJSONData;
  jObject : TJSONObject;
  jArray : TJSONArray;
  s : String;
begin
  // this is only a minimal sampling of what can be done with this API

  // create from string
  jData := GetJSON('{"Fld1" : "Hello", "Fld2" : 42, "Colors" : ["Red", "Green", "Blue"]}');

  // output as a flat string
  s := jData.AsJSON;

  // output as nicely formatted JSON
  s := jData.FormatJSON;

  // cast as TJSONObject to make access easier
  jObject := jData as TJSONObject;

  // retrieve value of Fld1
  s := jObject.Get('Fld1');

  // change value of Fld2
  jObject.Integers['Fld2'] := 123;

  // retrieve the second color
  s := jData.FindPath('Colors[1]').AsString;

  // add a new element
  jObject.Add('Happy', True);

  // add a new sub-array
  jArray := TJSONArray.Create;
  jArray.Add('North');
  jArray.Add('South');
  jArray.Add('East');
  jArray.Add('West');
  jObject.Add('Directions', jArray);

end;

Note: it's necessary to free jData when you have finished with it. Otherwise a memory leak is created.

Traversing Items

uses
  Classes, TypInfo, fpjson, jsonparser;

procedure JSONItems(Info: TStrings);
var
  jData : TJSONData;
  jItem : TJSONData;
  i, j: Integer;
  object_name, field_name, field_value, object_type, object_items: String;
begin
  jData := GetJSON('{"A":{"field1":0, "field2": false},"B":{"field1":0, "field2": false}}');

  for i := 0 to jData.Count - 1 do
  begin
    jItem := jData.Items[i];
 
    object_type := GetEnumName(TypeInfo(TJSONtype), Ord(jItem.JSONType));
    object_name := TJSONObject(jData).Names[i];
    WriteStr(object_items, jItem.Count);
 
    Info.Append('object type: ' + object_type + '|object name: ' + object_name + '|number of fields: ' + object_items);
 
    for j := 0 to jItem.Count - 1 do
    begin
      field_name := TJSONObject(jItem).Names[j];
      field_value := jItem.FindPath(TJSONObject(jItem).Names[j]).AsString;
 
      Info.Append(field_name + '|' + field_value);
    end;
  end;
 
  jData.Free;
end;

Save/load form position to/from file

uses
  jsonConf;

procedure SaveFormPos(AForm: TForm; const AFilename: string);
var
  c: TJSONConfig;
begin
  c:= TJSONConfig.Create(Nil);
  try
    //try/except to handle broken json file
    try
      c.Formatted:= true;
      c.Filename:= AFilename;
    except
      exit;
    end;

    c.SetValue('/dialog/max', AForm.WindowState=wsMaximized);
    if AForm.WindowState<>wsMaximized then
    begin
      c.SetValue('/dialog/posx', AForm.Left);
      c.SetValue('/dialog/posy', AForm.Top);
      c.SetValue('/dialog/sizex', AForm.Width);
      c.SetValue('/dialog/sizey', AForm.Height);
    end;
  finally
    c.Free;
  end;
end;

procedure LoadFormPos(AForm: TForm; const AFilename: string);
var
  nLeft, nTop, nW, nH: Integer;
  c: TJSONConfig;
begin
  c:= TJSONConfig.Create(Nil);
  try
    //try/except to handle broken json file
    try
      c.Formatted:= true;
      c.Filename:= AFilename;
    except
      exit;
    end;

    nLeft:= c.GetValue('/dialog/posx', AForm.Left);
    nTop:= c.GetValue('/dialog/posy', AForm.Top);
    nW:= c.GetValue('/dialog/sizex', AForm.Width);
    nH:= c.GetValue('/dialog/sizey', AForm.Height);
    AForm.SetBounds(nLeft, nTop, nW, nH);

    if c.GetValue('/dialog/max', false) then
      AForm.WindowState:= wsMaximized;
  finally
    c.Free;
  end;
end;

Save/load TStringList

uses
  jsonConf;
var
  cfg: TJSONConfig;
  List: TStringList;
  path: string;
begin
  List:= TStringList.Create;
  cfg:= TJSONConfig.Create(nil);
  try
    //try/except to handle broken json file
    try
      cfg.Formatted:= true;
      cfg.Filename:= AJsonFilename;
    except
      exit;
    end;

    cfg.GetValue('/mylist', List, '');
    List.Add('some_value');
    cfg.SetValue('/mylist', List);
  finally
    cfg.Free;
    List.Free;
  end;

From JsonViewer

Example usage can be found in the Lazarus jsonviewer tool (located in lazarus/tools/jsonviewer). In particular, this part of the tool shows how to use json:

procedure TMainForm.OpenFile(Const AFileName : String);
var
  S : TFileStream;
  P : TJSONParser;
  D : TJSONData;
begin
  S:=TFileStream.Create(AFileName,fmOpenRead);
  try
    P:=TJSONParser.Create(S);
    try
      P.Strict:=FStrict;
      D:=P.Parse;
    finally
      P.Free;
    end;
  finally
    S.Free;
  end;
  FFileName:=AFileName;
  SetCaption;
  FreeAndNil(FRoot);
  FRoot:=D;
  ShowJSONDocument;
end;

procedure TMainForm.ShowJSONDocument;
begin
  with TVJSON.Items do
  begin
    BeginUpdate;
    try
      TVJSON.Items.Clear;
      SHowJSONData(Nil,FRoot);
      with TVJSON do
        if (Items.Count>0) and Assigned(Items[0]) then
        begin
          Items[0].Expand(False);
          Selected:=Items[0];
        end;
    finally
      EndUpdate;
    end;
  end;
end;

procedure TMainForm.ShowJSONData(AParent : TTreeNode; Data : TJSONData);
var
  N,N2 : TTreeNode;
  I : Integer;
  D : TJSONData;
  C : String;
  S : TStringList;
begin
  N:=Nil;
  if Assigned(Data) then
  begin
    case Data.JSONType of
      jtArray,
      jtObject:
        begin
          if (Data.JSONType=jtArray) then
            C:=SArray
          else
            C:=SObject;
          N:=TVJSON.Items.AddChild(AParent,Format(C,[Data.Count]));
          S:=TstringList.Create;
          try
            for I:=0 to Data.Count-1 do
              if Data.JSONtype=jtArray then
                S.AddObject(IntToStr(I),Data.items[i])
              else
                S.AddObject(TJSONObject(Data).Names[i],Data.items[i]);
            if FSortObjectMembers and (Data.JSONType=jtObject) then
              S.Sort;
            for I:=0 to S.Count-1 do
            begin
              N2:=TVJSON.Items.AddChild(N,S[i]);
              D:=TJSONData(S.Objects[i]);
              N2.ImageIndex:=ImageTypeMap[D.JSONType];
              N2.SelectedIndex:=ImageTypeMap[D.JSONType];
              ShowJSONData(N2,D);
            end
          finally
            S.Free;
          end;
        end;
      jtNull:
        N:=TVJSON.Items.AddChild(AParent,SNull);
      else
        N:=TVJSON.Items.AddChild(AParent,Data.AsString);
    end;
    if Assigned(N) then
    begin
      N.ImageIndex:=ImageTypeMap[Data.JSONType];
      N.SelectedIndex:=ImageTypeMap[Data.JSONType];
      N.Data:=Data;
    end;
  end;
end;

Change formatting of float numbers

Q: My program generates and writes data to JSON file. I use FormatJSON() method to make output more readable. I'm not quite satisfied how numbers with float point look:

"coordinates" : [
     5.5978631048365003E+001,
     2.2100000000000000E+002
]

I want to see normal form:

"coordinates" : [
     55.978631048365003,
     221.0
]

A (forum members y.ivanov, rvk, wp):

{$mode objfpc}{$h+}
uses
  fpjson,
  jsonparser,
  SysUtils;
 
type
  TJSONFloat4Number = class(TJSONFloatNumber)
  protected
    function GetAsString: TJSONStringType; override;
  end;
 
function TJSONFloat4Number.GetAsString: TJSONStringType;
var
  F: TJSONFloat;
  fs: TFormatSettings;
begin
  fs := DefaultFormatSettings;
  fs.DecimalSeparator := '.';
  F := GetAsFloat;
  Result := FormatFloat('0.0###############', F, fs); // format with your preferences
end;

procedure JSONTest;
var
  jData: TJSONData;
begin
  jData := GetJSON('{"coordinates": [5.5978631048365003E+001, 2.2100000000000000E+002]}');
  writeln(jData.FormatJSON);
  jData.Free;
end;
 
begin
  SetJSONInstanceType(jitNumberFloat, TJSONFloat4Number);
  JSONTest;
  Readln;
end.

See also