Difference between revisions of "fcl-json"

From Free Pascal wiki
Jump to navigationJump to search
(Warning about getelement access violations)
 
(67 intermediate revisions by 14 users not shown)
Line 1: Line 1:
fcl-json - is [[JSON]] (Javascript Object Notation) implementation
+
{{fcl-json}}
  
== Notes ==
+
= General info =
Contains the fpjson JSON manipulation unit.
 
  
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]
+
fcl-json is a [[JSON]] implementation.  
You'd hav to use the .Find method (not available in FPC 2.6.0) to first check if the element/variable (price in this example) exists.
 
  
[[Package List]]
+
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|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.
 +
<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.
 +
 
 +
= 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 ==
 +
 
 +
<syntaxhighlight lang=pascal>
 +
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;
 +
 
 +
</syntaxhighlight>
 +
 
 +
Note: it's necessary to free jData when you have finished with it. Otherwise a memory leak is created.
 +
 
 +
== Traversing Items ==
 +
 
 +
<syntaxhighlight lang=pascal>
 +
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;
 +
</syntaxhighlight>
 +
 
 +
== Save/load form position to/from file==
 +
 
 +
<syntaxhighlight lang=pascal>
 +
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;
 +
</syntaxhighlight>
 +
 
 +
== Save/load TStringList ==
 +
 
 +
<syntaxhighlight lang=pascal>
 +
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; 
 +
</syntaxhighlight>
 +
 
 +
== 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:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
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; 
 +
</syntaxhighlight>
 +
 
 +
==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'''):
 +
<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 ==
 +
 
 +
* 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]]

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