/SuperObject.Delphi

Pascal (Delphi, FPC) json parser library SuperObject

Primary LanguagePascalThe UnlicenseUnlicense

SuperObject.Delphi

Pascal (Delphi, FPC) json parser library SuperObject.

This version is compatible with Delphi and last FPC complilers.

Changes

  • v1.2
    • support of currency data type
    • right trim unquoted string
    • read Unicode Files and streams (Litle Endian with BOM)
    • Fix bug on javadate functions - windows nt compatibility
    • Now you can force to parse only the canonical syntax of JSON using the stric parameter
    • Delphi 2010 RTTI marshalling
    • Delphi 10.4 Sydney RTTI marshalling of "Managed Records"
    • ...

What is JSON ?

  • JSON (JavaScript Object Notation) is a lightweight data-interchange format.
  • It is easy for humans to read and write.
  • It is easy for machines to parse and generate.
  • It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999.
  • JSON is a text format that is completely language independent but uses conventions that are familiar to programmers.
  • These properties make JSON an ideal data-interchange language.
  • You can get more informations on json.org.
{
  "name": "Jon Snow", /* this is a comment */
  "dead": true,
  "telephones": ["000000000", "111111111111"],
  "age": 33,
  "size": 1.83,
  "adresses": [
    {
      "adress": "foo",
      "city": "The wall",
      "pc": 57000
    },
    {
      "adress": "foo",
      "city": "Winterfell",
      "pc": 44000
    }
  ]
}

Parsing a JSON data structure

var
  obj: ISuperObject;
begin
  obj := SO('{"foo": true}');
  obj := TSuperObject.ParseString('{"foo": true}');
  obj := TSuperObject.ParseStream(stream);
  obj := TSuperObject.ParseFile(FileName);
end;

Accessing data

There isn't individual datastructure for each supported data types. They are all an object: the ISuperObject.

  val := obj.AsString;
  val := obj.AsInteger;
  val := obj.AsBoolean;
  val := obj.AsDouble;
  val := obj.AsArray;
  val := obj.AsObject;
  val := obj.AsMethod;

How to read a property value of an object ?

  val := obj.AsObject.S['foo']; // get a string
  val := obj.AsObject.I['foo']; // get an Int64
  val := obj.AsObject.B['foo']; // get a Boolean
  val := obj.AsObject.D['foo']; // get a Double
  val := obj.AsObject.O['foo']; // get an Object (default)
  val := obj.AsObject.M['foo']; // get a Method
  val := obj.AsObject.N['foo']; // get a null object

How to read a value from an array ?

  // the advanced way
  val := obj.AsArray.S[0]; // get a string
  val := obj.AsArray.I[0]; // get an Int64
  val := obj.AsArray.B[0]; // get a Boolean
  val := obj.AsArray.D[0]; // get a Double
  val := obj.AsArray.O[0]; // get an Object (default)
  val := obj.AsArray.M[0]; // get a Method
  val := obj.AsArray.N[0]; // get a null object

Using paths

Using paths is a very productive method to find an object when you know where is it. This is some usage cases:

  obj['foo']; // get a property
  obj['123']; // get an item array
  obj['foo.list']; // get a property from an object
  obj['foo[123]']; // get an item array from an object
  obj['foo(1,2,3)']; // call a method
  obj['foo[]'] := value; // add an item array

you also can encapsulate paths:

  obj := so('{"index": 1, "items": ["item 1", "item 2", "item 3"]}');
  obj['items[index]'] // return "item 2"

or recreate a new data structure from another:

  obj := so('{"index": 1, "items": ["item 1", "item 2", "item 3"]}');
  obj['{"item": items[index], "index": index}'] // return {"item": "item 2", "index": 1}

Browsing data structure

Using Delphi enumerator.

Using Delphi enumerator you can browse item's array or property's object value in the same maner.

var
  item: ISuperObject;
begin
  for item in obj['items'] do ...

you can also browse the keys and values of an object like this:

var
  item: TSuperAvlEntry;
begin
  for item in obj.AsObject do ...
  begin
    item.Name;
    item.Value;
  end;

Browsing object properties without enumerator

var
  item: TSuperObjectIter;
begin
  if ObjectFindFirst(obj, item) then
  repeat
    item.key;
    item.val;
  until not ObjectFindNext(item);
  ObjectFindClose(item);

Browsing array items without enumerator

var
  item: Integer;
begin
  for item := 0 to obj.AsArray.Length - 1 do
    obj.AsArray[item]

RTTI & marshalling in Delphi 2010

type
  TData = record
    str: string;
    int: Integer;
    bool: Boolean;
    flt: Double;
  end;
var
  ctx: TSuperRttiContext;
  data: TData;
  obj: ISuperObject;
begin
  ctx := TSuperRttiContext.Create;
  try
    data := ctx.AsType<TData>(SO('{str: "foo", int: 123, bool: true, flt: 1.23}'));
    obj := ctx.AsJson<TData>(data);
  finally
    ctx.Free;
  end;
end;

Saving data

  obj.AsJSon(options);
  obj.SaveTo(stream);
  obj.SaveTo(filename);

Helpers

  SO(['prop1', true, 'prop2', 123]); // return an object {"prop1": true, "prop2": 123}
  SA([true, 123]); // return an array [true, 123]

Non canonical forms

The SuperObject is able to parse non canonical forms.

// unquoted identifiers
SO('{foo: true}');
// unescaped or unquoted strings
SO('{собственность: bla bla bla}');
// excadecimal
SO('{foo: \xFF}');

Additional examples

Get a Json string:

 function TDataModule3.GetSearchLostStatus: string;
 var
  X: ISuperObject;
 begin
  X := SO;
  with FServiceThread do
  begin
   X.B['NowSearchForFile'] := NowSearchForFile;
   X.S['LastLostFile'] := LastLostFile;
   X.B['NowSearchForRec'] := NowSearchForRec;
   X.S['LostRecProgress'] := LostRecProgress;
  end;
  Result := X.AsJSON;
 end;

Get data back from the Json string:

 procedure TForm2.SetSearchLostStatus(const Params: string);
 var
  s: string;
  X: ISuperObject;
 begin
  X := TSuperObject.ParseString(PChar(Params), True);
  FNowSearchForFile := not X.B['NowSearchForFile'];
  FLastLostFile := X.S['LastLostFile'];
  FNowSearchForRec := X.B['NowSearchForRec'];
  FLostRecProgress := X.I['LostRecProgress'];
 end;

An array usage:

 procedure TForm4.SetQueueStatus(const Params: string);
 var
  j: integer;
  X:  ISuperObject;
  X1: TSuperArray;
  s: string;
 begin
  X  := TSuperObject.ParseString(PChar(s), True);
  X1 := X.A['Queues'];
  StringGrid1.RowCount := X1.Length + 1;
  for j := 0 to X1.Length - 1 do
   with StringGrid1, X1[j] do
   begin
    Cells[0, j + 1] := S['NAME'];
    if j = 0 then
     Cells[1, j + 1] := S['FILES_QUEUED1'] + ' + ' + S['FILES_QUEUED2']
    else
     Cells[1, j + 1] := S['FILES_QUEUED'];
    Cells[2, j + 1] := S['STATUS'];
    Cells[3, j + 1] := S['FILES_DONE'];
    Cells[4, j + 1] := S['FILES_SKIPPED'];
   end;
 end;

Path usage:

 var s: string; json, field: iSuperObject;
 ....
  field := json.O['Document.CurrentItems.DocumentItem.Fields.FieldValue[1].Value'];
  s := json.O['Value'].AsString;