/// minimum standand-alone cross-platform JSON process using variants
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynCrossPlatformJSON;

{
    This file is part of Synopse mORMot framework.

    Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez
      Synopse Informatique - https://synopse.info

  *** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.

  The Original Code is Synopse mORMot framework.

  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2022
  the Initial Developer. All Rights Reserved.

  Contributor(s):
  - Witya
  
  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the GPL or the LGPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

  Should compile with Delphi for any platform (including NextGen for mobiles),
    with FPC 2.7 or Kylix, and with SmartMobileStudio 2+
  - FPC prior to 2.7.1 has some issues with working with variants: UTF-8
    encoding is sometimes lost, and TInvokeableVariantType.SetProperty is broken

}

{$i SynCrossPlatform.inc} // define e.g. HASINLINE

interface

uses
  SysUtils,
  Classes,
{$ifdef NEXTGEN}
  System.Generics.Collections,
{$endif}
{$ifndef NEXTGEN}
  Contnrs,
{$endif}
  Variants,
  TypInfo;

type
  TStringDynArray = array of string;
  TVariantDynArray = array of variant;
  TIntegerDynArray = array of integer;

  /// this type is used to store BLOB content
  TByteDynArray = array of byte;
  PByteDynArray = ^TByteDynArray;

  {$ifdef FPC}
  NativeInt = PtrInt;
  NativeUInt = PtrUInt;
  {$else}
  {$ifndef ISDELPHI2010} // Delphi 2009 NativeUInt is buggy
  NativeInt = integer;
  NativeUInt = cardinal;
  {$endif}
  {$ifndef UNICODE}
  RawByteString = AnsiString;
  {$endif}
  {$endif}

  // this type will store UTF-8 encoded buffer (also on NextGen platform)
  {$ifdef NEXTGEN}
  TUTF8Buffer = TBytes;
  // TObjecTList is not defined in Mobile platforms
  TObjectList = TObjectList<TObject>;
  {$else}
  TUTF8Buffer = UTF8String;
  {$endif}

  /// exception used during standand-alone cross-platform JSON process
  EJSONException = class(Exception);

  /// which kind of document the TJSONVariantData contains
  TJSONVariantKind = (jvUndefined, jvObject, jvArray);

  PJSONVariantData = ^TJSONVariantData;

  {$A-}
  /// stores any JSON object or array as variant
  // - this structure is not very optimized for speed or memory use, but is
  // simple and strong enough for our client-side purpose
  // - it is in fact already faster (and using less memory) than DBXJSON and
  // SuperObject / XSuperObject libraries - of course, mORMot's TDocVariant
  // is faster, as dwsJSON is in some cases, but those are not cross-platform
  {$ifdef USEOBJECTINSTEADOFRECORD}
  TJSONVariantData = object
  protected
  {$else}
  TJSONVariantData = record
  private
  {$endif}
    VType: TVarType;
    _Align: byte;
    VKind: TJSONVariantKind;
    VCount: integer;
    function GetKind: TJSONVariantKind;
    function GetCount: integer;
    function GetVarData(const aName: string; var Dest: TVarData): boolean;
    function GetValue(const aName: string): variant;
    function GetValueCopy(const aName: string): variant;
    procedure SetValue(const aName: string; const aValue: variant);
    function GetItem(aIndex: integer): variant;
    procedure SetItem(aIndex: integer; const aItem: variant);
  public
    /// names of this jvObject
    Names: TStringDynArray;
    /// values of this jvObject or jvArray
    Values: TVariantDynArray;
    /// initialize the low-level memory structure
    // - you should call Clear before calling overloaded Init several times
    procedure Init; overload;
    /// initialize the low-level memory structure with a given JSON content
    // - you should call Clear before calling overloaded Init several times
    procedure Init(const JSON: string); overload;
    /// initialize the low-level memory structure with a given array of variant
    // - you should call Clear before calling overloaded Init several times
    procedure InitFrom(const aValues: TVariantDynArray); overload;
    /// delete all internal stored data
    // - basically the same as Finalize(aJsonVariantData) + aJsonVariantData.Init
    // - you should call this method before calling overloaded Init several times
    procedure Clear;
    /// access to a nested TJSONVariantData item
    // - returns nil if aName was not found, or not a true TJSONVariantData item
    function Data(const aName: string): PJSONVariantData;
      {$ifdef HASINLINE}inline;{$endif}
    /// access to a nested TJSONVariantData item, creating it if necessary
    // - aPath can be specified with any depth, e.g. 'level1.level2.level3' 
    // - if the item does not exist or is not a true TJSONVariantData, a new
    // one will be created, and returned as pointer
    function EnsureData(const aPath: string): PJSONVariantData;
    /// add a void TJSONVariantData to the jvArray and return a pointer to it
    function AddItem: PJSONVariantData;
    /// add a value to the jvArray
    // - raise a ESJONException if the instance is a jvObject
    procedure AddValue(const aValue: variant);
    /// add a name/value pair to the jvObject
    // - raise a ESJONException if the instance is a jvArray
    procedure AddNameValue(const aName: string; const aValue: variant);
    /// search for a name in this jvObject
    function NameIndex(const aName: string): integer;
    /// set a value of this jvObject to a given path
    // - aPath can be specified with any depth, e.g. 'level1.level2.level3' 
    procedure SetPath(const aPath: string; const aValue: variant); 
    /// fill this document from a JSON array or object
    function FromJSON(const JSON: string): boolean;
    /// convert this document into JSON array or object
    function ToJSON: string;
    /// fill the published properties of supplied class from this JSON object
    function ToObject(Instance: TObject): boolean;
    /// create an instance, and fill its published properties from this JSON object
    // - it should contain some "ClassName" properties, i.e. JSON should have
    // been created by ObjectToJSON(Instance,true) and the class should have
    // been registered with RegisterClassForJSON()
    function ToNewObject: TObject;
    /// kind of document this TJSONVariantData contains
    // - returns jvUndefined if this instance is not a TJSONVariant custom variant
    property Kind: TJSONVariantKind read GetKind;
    /// number of items in this jvObject or jvArray
    // - returns 0 if this instance is not a TJSONVariant custom variant
    property Count: integer read GetCount;
    /// access by name to a value of this jvObject
    // - value is returned as (varVariant or varByRef) for best speed
    // - will return UnAssigned if aName is not correct or this is not a jvObject
    property Value[const aName: string]: variant read GetValue write SetValue; default;
    /// access by name to a value of this jvObject
    // - value is returned as a true copy (not varByRef) so this property is
    // slower but safer than Value[], if the owning TJsonVariantData disappears
    // - will return UnAssigned if aName is not correct or this is not a jvObject
    property ValueCopy[const aName: string]: variant read GetValueCopy;
    /// access by index to a value of this jvArray
    // - will return UnAssigned if aIndex is not correct or this is not a jvArray
    property Item[aIndex: integer]: variant read GetItem write SetItem;
  end;
  {$A+}

  /// low-level class used to register TJSONVariantData as custom type
  // - allows late binding to values, e.g.
  // ! jsonvar.avalue := jsonvar.avalue+1;
  // - due to an issue with FPC implementation, you can only read properties,
  // not set them, so you should write:
  // ! TJSONVariantData(jsonvar)['avalue'] := jsonvar.avalue+1;
  TJSONVariant = class(TInvokeableVariantType)
  protected
    {$ifndef FPC}
    {$ifndef ISDELPHI6}
    function FixupIdent(const AText: string): string; override;
    {$endif}
    {$endif}
  public
    procedure Copy(var Dest: TVarData; const Source: TVarData;
      const Indirect: Boolean); override;
    procedure Clear(var V: TVarData); override;
    function GetProperty(var Dest: TVarData; const V: TVarData;
      const Name: string): Boolean; override;
    {$ifdef FPC_VARIANTSETVAR} // see http://mantis.freepascal.org/view.php?id=26773
    function SetProperty(var V: TVarData; const Name: string;
      const Value: TVarData): Boolean; override;
    {$else}
    function SetProperty(const V: TVarData; const Name: string;
      const Value: TVarData): Boolean; override;
    {$endif}
    procedure Cast(var Dest: TVarData; const Source: TVarData); override;
    procedure CastTo(var Dest: TVarData; const Source: TVarData;
      const AVarType: TVarType); override;
  end;

  /// handle a JSON result table, as returned by mORMot's server
  // - handle both expanded and non expanded layout
  // - will be used e.g. on client side for variant-based ORM data parsing
  TJSONTable = class
  protected
    fJSON: string;
    fFieldNames: TStringDynArray;
    fJSONExpanded: boolean;
    fJSONIndexFirstValue: integer;
    fJSONCurrentIndex: integer;
    fRowValues: TVariantDynArray;
    function Get(const FieldName: string): variant;
  public
    /// parse the supplied JSON content
    constructor Create(const aJSON: string);
    /// case-insensitive search for a field name
    function FieldIndex(const FieldName: string): integer;
    /// to be called in a loop to iterate through all data rows
    // - if returned true, Value[] contains the fields of this row
    function Step(SeekFirst: boolean=false): boolean; 
    /// to be called in a loop to iterate through all data rows
    // - if returned true, RowValues contains this row as TJSONVariant
    function StepValue(var RowValues: variant; SeekFirst: boolean=false): boolean;
    /// after Step() returned true, can be used to retrieve a field value by name
    property Value[const FieldName: string]: variant read Get; default;
    /// after Step() returned true, can be used to retrieve a field value by index
    property RowValues: TVariantDynArray read fRowValues;
    /// the recognized field names
    property FieldNames: TStringDynArray read fFieldNames;
    /// the associated JSON content
    property JSON: string read fJSON;
  end;

  /// an abstract type used for RTTI type information  
  TRTTITypeInfo = PPropInfo;

  /// an abstract type used for RTTI property information
  TRTTIPropInfo = PPropInfo;

  TRTTIPropInfoDynArray = array of TRTTIPropInfo;

  /// handle a JSON result table, as returned by mORMot's server
  // - handle both expanded and non expanded layout
  // - this class is able to use RTTI to fill all published properties of
  // a TObject
  TJSONTableObject = class(TJSONTable)
  protected
    fTypeInfo: pointer;
    fPropInfo: array of TRTTIPropInfo;
    procedure FillPropInfo(aTypeInfo: TRTTITypeInfo); virtual;
    procedure FillInstance(Instance: TObject); virtual;
    function GetPropInfo(aTypeInfo: TRTTITypeInfo; const PropName: string): TRTTIPropInfo; virtual;
  public
    /// to be called in a loop to iterate through all data rows
    // - if returned true, Object published properties will contain this row
    function StepObject(Instance: TObject; SeekFirst: boolean=false): boolean; virtual;
  end;

  /// used e.g. by TSynTest for each test case
  TPublishedMethod = record
    Name: string;
    Method: TMethod;
  end;
  /// as filled by GetPublishedMethods()
  TPublishedMethodDynArray = array of TPublishedMethod;


/// create a TJSONVariant instance from a given JSON content
// - typical usage may be:
//! var doc: variant;
//!     json: string;
//! begin
//!   doc := JSONVariant('{"test":1234,"name":"Joh\"n\r"}');
//!   assert(doc.test=1234); // access via late binding
//!   assert(doc.name='Joh"n'#13);
//!   assert(doc.name2=null); // unknown properties returns null
//!   json := doc; // to convert a TJSONVariant to JSON, just assign to a string
//!   assert(json='{"test":1234,"name":"Joh\"n\r"}');
//! end;
// - note that FPC does not allow to set values by late-binding
function JSONVariant(const JSON: string): variant; overload;

/// create a TJSONVariant TJSONVariant array from a supplied array of values
function JSONVariant(const values: TVariantDynArray): variant; overload;

/// create a TJSONVariant TJSONVariant array from a supplied array of values
function JSONVariantFromConst(const constValues: array of variant): variant; 

/// access to a TJSONVariant instance members
// - e.g. Kind, Count, Names[] or Values[]
// - will raise an exception if the supplied variant is not a TJSONVariant
// - this function is safer than TJSONVariant(JSONVariant)
function JSONVariantData(const JSONVariant: variant): PJSONVariantData;

/// access to a TJSONVariant instance members
// - e.g. Kind, Count, Names[] or Values[]
// - will return a read-only fake TJSONVariant with Kind=jvUndefined if the
// supplied variant is not a TJSONVariant
// - if ExpectedKind is jvArray of jvObject, it would return a fake TJSONVariant
// with Kind=jvUndefined if the JSONVariant kind does not match - so you can write:
// !var _a: integer;
// !    _arr: PJSONVariantData;
// !...
// !   _arr := JSONVariantDataSafe(_variant,jvArray);
// !   SetLength(result,_arr.Count);
// !   for _a := 0 to _arr.Count-1 do
// !     result[_a] := _arr.Values[_a];
// in the above code, _arr.Count will be 0 if _variant.Kind<>jvArray
// - this function is safer than TJSONVariant(JSONVariant)
function JSONVariantDataSafe(const JSONVariant: variant;
  ExpectedKind: TJSONVariantKind=jvUndefined): PJSONVariantData;

var
  /// the custom variant type definition registered for TJSONVariant
  JSONVariantType: TInvokeableVariantType;


/// compute the quoted JSON string corresponding to the supplied text
function StringToJSON(const Text: string): string;

/// compute the JSON representation of a floating-point value
procedure DoubleToJSON(Value: double; var result: string);

/// compute the ISO-8601 JSON text representation of a date/time value
// - e.g. "YYYY-MM-DD" "Thh:mm:ss" or "YYYY-MM-DDThh:mm:ss"
// - if Date is 0, will return ""
function DateTimeToJSON(Value: TDateTime): string;

/// compute the JSON representation of a variant value
// - will work for simple types, or TJSONVariant object or array
function ValueToJSON(const Value: variant): string;

/// compute a variant from its JSON representation
// - will work for simple types, or TJSONVariant object or array
function JSONToValue(const JSON: string): variant;

/// compute the ISO-8601 JSON text representation of the current date/time value
// - e.g. "2015-06-27T20:59:29"
function NowToIso8601: string;

/// compute the unquoted ISO-8601 text representation of a date/time value
// - e.g. 'YYYY-MM-DD' 'Thh:mm:ss' or 'YYYY-MM-DDThh:mm:ss'
// - if Date is 0, will return ''
function DateTimeToIso8601(Value: TDateTime): string;

/// convert unquoted ISO-8601 text representation into a date/time value
// - e.g. 'YYYY-MM-DD' 'Thh:mm:ss' or 'YYYY-MM-DDThh:mm:ss'
function Iso8601ToDateTime(const Value: string): TDateTime;

/// compute the JSON representation of an object published properties
// - handle only simple types of properties, not nested class instances
// - any TList/TObjectList/TCollection will be serialized as JSON array
function ObjectToJSON(Instance: TObject; StoreClassName: boolean=false): string;

/// fill an object published properties from the supplied JSON object
// - handle only simple types of properties, not nested class instances
function JSONToObject(Instance: TObject; const JSON: string): boolean;

/// create a new object and fil its published properties from the supplied
// JSON object, which should include "ClassName":"..." properties
// - JSON should have been created with ObjectToJSON(Instance,true) and
// the class should have been registered with RegisterClassForJSON()
function JSONToNewObject(const JSON: string): pointer;

/// register the class types to be created from its name
// - used e.g. by JSONToNewObject() or TJSONVariantData.ToNewObject
procedure RegisterClassForJSON(const Classes: array of TClass);

/// create a class instance from its name
// - the class should have been registered previously via RegisterClassForJSON()
// - if the supplied class name is not found, will return nil
function CreateClassForJSON(const ClassName: string): TObject;

/// create a list of object published properties from the supplied JSON object
// - handle only simple types of properties, not nested class instances
function JSONToObjectList(ItemClass: TClass; const JSON: string): TObjectList;

/// return a string corresponding to the type name, as stored in the RTTI
// - e.g. 'TDateTime', 'TByteDynArray', 'TModTime', 'TCreateTime'
function RTTIPropInfoTypeName(PropInfo: TRTTIPropInfo): string;

/// retrieve the published properties type information about a given class
procedure GetPropsInfo(TypeInfo: TRTTITypeInfo; var PropNames: TStringDynArray;
  var PropRTTI: TRTTIPropInfoDynArray);

/// retrieve the value of a published property as variant
function GetInstanceProp(Instance: TObject; PropInfo: TRTTIPropInfo; StoreClassName: boolean = False): variant;

/// set the value of a published property from a variant
procedure SetInstanceProp(Instance: TObject; PropInfo: TRTTIPropInfo;
  const Value: variant);

/// retrieve all the published methods of a given class, using RTTI
procedure GetPublishedMethods(Instance: TObject; out Methods: TPublishedMethodDynArray);

/// convert an "array of const" parameter value into its string representation
function VarRecToValue(const V: TVarRec; out wasString: boolean): string;

/// convert the supplied text as "text", as expected by SQL standard
procedure DoubleQuoteStr(var text: string);

/// decode a Base64-encoded string
// - default withBase64Magic=TRUE will expect the string to start with our
// JSON_BASE64_MAGIC marker
function Base64JSONStringToBytes(const JSONString: string;
  var Bytes: TByteDynArray; withBase64Magic: boolean=true): boolean;

/// Base-64 encode a BLOB into string
// - default withBase64Magic=TRUE will include our JSON_BASE64_MAGIC marker
function BytesToBase64JSONString(const Bytes: TByteDynArray;
  withBase64Magic: boolean=true): string;

const
  /// special code to mark Base64 binary content in JSON string
  // - Unicode special char U+FFF0 is UTF-8 encoded as EF BF B0 bytes
  // - prior to Delphi 2009, it won't work as expected since U+FFF0 won't be
  // able to be converted into U+FFF0
  {$ifdef UNICODE}
  JSON_BASE64_MAGIC: word = $fff0;
  {$else}
  JSON_BASE64_MAGIC: array[0..2] of byte = ($ef,$bf,$b0);
  {$endif}

  /// size, in platform chars, of our special code to mark Base64 binary
  // content in JSON string
  // - equals 1 since Delphi 2009 (UTF-16 encoded), or 3 for older versions
  // (UTF-8encoded) of the compiler compiler
  JSON_BASE64_MAGIC_LEN = sizeof(JSON_BASE64_MAGIC) div sizeof(char);

{$ifndef ISSMS}
/// read an UTF-8 (JSON) file into a native string
// - file should be existing, otherwise an exception is raised
function UTF8FileToString(const aFileName: TFileName): string;
{$endif}

/// this function is faster than str := str+chr !
procedure AppendChar(var str: string; chr: Char);
  {$ifdef HASINLINE}inline;{$endif}

/// check that two ASCII-7 latin text do match
function IdemPropName(const PropName1,PropName2: string): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// check that two ASCII-7 latin text do match
// - first parameter is expected to be a shortstring low-level buffer - as such,
// this overloaded function would work with NEXTGEN encoded RTTI
function IdemPropName(PropName1: PByteArray; const PropName2: string): boolean; overload;
  {$ifdef HASINLINE}inline;{$endif}

/// convert ASCII-7 latin text, encoded as a shortstring buffer, into a string
// - as such, this function would work with NEXTGEN encoded RTTI
function ShortStringToString(Buffer: PByteArray): string;

/// check that two ASCII-7 latin text do match
function StartWithPropName(const PropName1,PropName2: string): boolean;


implementation

function IdemPropName(const PropName1,PropName2: string): boolean;
var L,i: integer;
begin
  result := false;
  L := length(PropName2);
  if length(PropName1)<>L then
    exit;
  for i := 1 to L do
    if (ord(PropName1[i]) xor ord(PropName2[i])) and
      {$ifdef UNICODE}$ffdf{$else}$df{$endif}<>0 then
      exit;
  result := true;
end;

function ShortStringToString(Buffer: PByteArray): string;
{$ifdef UNICODE}
var i: integer;
begin
  SetLength(result,Buffer^[0]);
  for i := 1 to Buffer^[0] do
    result[i] := chr(Buffer^[i]);
end;
{$else}
begin
  SetString(result,PAnsiChar(@Buffer^[1]),Buffer^[0]);
end;
{$endif}

function IdemPropName(PropName1: PByteArray; const PropName2: string): boolean;
var L,i: integer;
begin
  result := false;
  L := length(PropName2);
  if PropName1^[0]<>L then
    exit;
  for i := 1 to L do
    if (PropName1^[i] xor ord(PropName2[i])) and
      {$ifdef UNICODE}$ffdf{$else}$df{$endif}<>0 then
      exit;
  result := true;
end;

function StartWithPropName(const PropName1,PropName2: string): boolean;
var L,i: integer;
begin
  result := false;
  L := length(PropName2);
  if length(PropName1)<L then
    exit;
  for i := 1 to L do
    if (ord(PropName1[i]) xor ord(PropName2[i])) and
      {$ifdef UNICODE}$ffdf{$else}$df{$endif}<>0 then
      exit;
  result := true;
end;

{$ifndef ISSMS} // there is no file within HTML5 DOM

{$ifdef FPC}
// assume string is UTF-8 encoded (as with Lazarus/LCL)
// note that when working with variants, FPC 2.7.1 sometimes clear the code page
type UTF8ToString = RawByteString;
{$else}
{$ifndef UNICODE}
function UTF8ToString(const utf8: TUTF8Buffer): string;
begin
  result := UTF8ToAnsi(utf8);
end;
{$endif}
{$endif}

function UTF8FileToString(const aFileName: TFileName): string;
var F: TFileStream;
    len: integer;
    utf8: TUTF8Buffer;
begin
  F := TFileStream.Create(aFileName,fmOpenRead);
  try
    len := F.Size;
    SetLength(utf8,len);
    {$ifdef NEXTGEN}
    F.Read(utf8[0],len);
    result := TEncoding.UTF8.GetString(utf8);
    {$else}
    F.Read(utf8[1],len);
    result := UTF8ToString(utf8);
    {$endif}
  finally
    F.Free;
  end;
end;
{$endif}

function JSONVariant(const JSON: string): variant;
begin
  VarClear(result);
  TJSONVariantData(result).FromJSON(JSON);
end;

function JSONVariant(const values: TVariantDynArray): variant;
begin
  VarClear(result);
  TJSONVariantData(result).Init;
  TJSONVariantData(result).VKind := jvArray;
  TJSONVariantData(result).VCount := length(values);
  TJSONVariantData(result).Values := values;
end;

function JSONVariantFromConst(const constValues: array of variant): variant; 
var i: integer;
begin
  VarClear(result);
  with TJSONVariantData(result) do begin
    Init;
    VKind := jvArray;
    VCount := length(values);
    SetLength(Values,VCount);
    for i := 0 to VCount-1 do
      Values[i] := constValues[i];
  end;
end;

function JSONVariantData(const JSONVariant: variant): PJSONVariantData;
begin
  with TVarData(JSONVariant) do
    if VType=JSONVariantType.VarType then
      result := @JSONVariant else
    if VType=varByRef or varVariant then
      result := JSONVariantData(PVariant(VPointer)^) else
    raise EJSONException.CreateFmt('JSONVariantData.Data(%d<>JSONVariant)',[VType]);
end;

const // will be in code section of the exe, so will be read-only by design
  JSONVariantDataFake: TJSONVariantData = ();

function JSONVariantDataSafe(const JSONVariant: variant;
  ExpectedKind: TJSONVariantKind=jvUndefined): PJSONVariantData;
begin
  with TVarData(JSONVariant) do
    if VType=JSONVariantType.VarType then
      if (ExpectedKind=jvUndefined) or
         (TJSONVariantData(JSONVariant).VKind=ExpectedKind) then
        result := @JSONVariant else
        result := @JSONVariantDataFake else
    if VType=varByRef or varVariant then
      result := JSONVariantDataSafe(PVariant(VPointer)^) else
      result := @JSONVariantDataFake;
end;

procedure AppendChar(var str: string; chr: Char);
{$ifdef ISSMS} // JavaScript immutable strings
begin
  str := str+chr
end;
{$else}
var len: Integer;
begin // str := str+chr would have created a temporary string for chr
  len := length(str);
  SetLength(str,len+1);
  PChar(pointer(str))[len] := chr; // SetLength() made str unique
end; 
{$endif}

function StringToJSON(const Text: string): string;
var len,j: integer;
  procedure DoEscape;
  var i: Integer;
  begin
    result := '"'+copy(Text,1,j-1); // here FPC 2.7.1 erases UTF-8 encoding
    for i := j to len do begin
      case Text[i] of
      #8:  result := result+'\b';
      #9:  result := result+'\t';
      #10: result := result+'\n';
      #12: result := result+'\f';
      #13: result := result+'\r';
      '\': result := result+'\\';
      '"': result := result+'\"';
      else
      if Text[i]<' ' then
        result := result+'\u00'+IntToHex(ord(Text[i]),2) else
        AppendChar(result,Text[i]); // will be UTF-8 encoded later
      end;
    end;
    AppendChar(result,'"');
  end;
begin
  len := length(Text);
  for j := 1 to len do
    case Text[j] of
    #0..#31,'\','"': begin
      DoEscape;
      exit;
    end;
    end;
  // if we reached here, no character needs to be escaped in this string
  result := '"'+Text+'"'; // here FPC 2.7.1 erases UTF-8 encoding :(
end;

{$ifdef KYLIX}
  {$define NOFORMATSETTINGS}
{$endif}
{$ifdef ISDELPHI6}
  {$define NOFORMATSETTINGS}
{$endif}

{$ifdef NOFORMATSETTINGS}
procedure DoubleToJSON(Value: double; var result: string);
var decsep: Char;
begin // warning: this is NOT thread-safe if you mix settings
  decsep := DecimalSeparator;
  result := FloatToStr(Value);
  DecimalSeparator := decsep;
end;
{$else}
var
  SettingsUS: TFormatSettings
  {$ifdef FPC} = (
      CurrencyFormat: 1;
      NegCurrFormat: 5;
      ThousandSeparator: ',';
      DecimalSeparator: '.';
      CurrencyDecimals: 2;
      DateSeparator: '-';
      TimeSeparator: ':';
      ListSeparator: ',';
      CurrencyString: '$';
      ShortDateFormat: 'd/m/y';
      LongDateFormat: 'dd" "mmmm" "yyyy';
      TimeAMString: 'AM';
      TimePMString: 'PM';
      ShortTimeFormat: 'hh:nn';
      LongTimeFormat: 'hh:nn:ss';
      ShortMonthNames: ('Jan','Feb','Mar','Apr','May','Jun',
                        'Jul','Aug','Sep','Oct','Nov','Dec');
      LongMonthNames: ('January','February','March','April','May','June',
                       'July','August','September','October','November','December');
      ShortDayNames: ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
      LongDayNames:  ('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
      TwoDigitYearCenturyWindow: 50;)
  {$endif};
procedure DoubleToJSON(Value: double; var result: string);
begin
  result := FloatToStr(Value,SettingsUS);
end;
{$endif}

function DateTimeToJSON(Value: TDateTime): string;
begin // e.g. "YYYY-MM-DD" "Thh:mm:ss" or "YYYY-MM-DDThh:mm:ss"
  result := '"'+DateTimeToIso8601(Value)+'"';
end;

function NowToIso8601: string;
begin
  result := DateTimeToIso8601(Now);
end;

function DateTimeToIso8601(Value: TDateTime): string;
begin // e.g. YYYY-MM-DD Thh:mm:ss or YYYY-MM-DDThh:mm:ss
  if Value=0 then
    result := '' else
  if frac(Value)=0 then
    result := FormatDateTime('yyyy"-"mm"-"dd',Value) else
  if trunc(Value)=0 then
    result := FormatDateTime('"T"hh":"nn":"ss',Value) else
    result := FormatDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss',Value);
end;

function Iso8601ToDateTime(const Value: string): TDateTime;
var Y,M,D, HH,MI,SS: cardinal;
begin //  YYYY-MM-DD   Thh:mm:ss  or  YYYY-MM-DDThh:mm:ss
      //  1234567890   123456789      1234567890123456789
  result := 0;
  case Length(Value) of
  9: if (Value[1]='T') and (Value[4]=':') and (Value[7]=':') then begin
    HH := ord(Value[2])*10+ord(Value[3])-(48+480);
    MI := ord(Value[5])*10+ord(Value[6])-(48+480);
    SS := ord(Value[8])*10+ord(Value[9])-(48+480);
    if (HH<24) and (MI<60) and (SS<60) then
      result := EncodeTime(HH,MI,SS,0);
  end;
  10: if (Value[5]=Value[8]) and (ord(Value[8]) in [ord('-'),ord('/')]) then begin
    Y := ord(Value[1])*1000+ord(Value[2])*100+
         ord(Value[3])*10+ord(Value[4])-(48+480+4800+48000);
    M := ord(Value[6])*10+ord(Value[7])-(48+480);
    D := ord(Value[9])*10+ord(Value[10])-(48+480);
    if (Y<=9999) and ((M-1)<12) and ((D-1)<31) then
      result := EncodeDate(Y,M,D);
  end;
  19: if (Value[5]=Value[8]) and (ord(Value[8]) in [ord('-'),ord('/')]) and
         (ord(Value[11]) in [ord(' '),ord('T')]) and (Value[14]=':') and (Value[17]=':') then begin
    Y := ord(Value[1])*1000+ord(Value[2])*100+
         ord(Value[3])*10+ord(Value[4])-(48+480+4800+48000);
    M := ord(Value[6])*10+ord(Value[7])-(48+480);
    D := ord(Value[9])*10+ord(Value[10])-(48+480);
    HH := ord(Value[12])*10+ord(Value[13])-(48+480);
    MI := ord(Value[15])*10+ord(Value[16])-(48+480);
    SS := ord(Value[18])*10+ord(Value[19])-(48+480);
    if (Y<=9999) and ((M-1)<12) and ((D-1)<31) and
       (HH<24) and (MI<60) and (SS<60) then
      result := EncodeDate(Y,M,D)+EncodeTime(HH,MI,SS,0);
  end;
  end;
end;

function ValueToJSON(const Value: variant): string;
var I64: Int64;
begin
  if TVarData(Value).VType=JSONVariantType.VarType then
    result := TJSONVariantData(Value).ToJSON else
  if (TVarData(Value).VType=varByRef or varVariant) then
    result := ValueToJSON(PVariant(TVarData(Value).VPointer)^) else
  if TVarData(Value).VType<=varNull then
    result := 'null' else
  if TVarData(Value).VType=varBoolean then
    if TVarData(Value).VBoolean then
      result := 'true' else
      result := 'false' else
  if TVarData(Value).VType=varDate then
    result := DateTimeToJSON(TVarData(Value).VDouble) else
  if VarIsOrdinal(Value) then begin
    I64 := Value;
    result := IntToStr(I64);
  end else
  if VarIsFloat(Value) then
    DoubleToJSON(Value,result) else
  if VarIsStr(Value) then
    result := StringToJSON(Value) else
    result := Value;
end;

function VarRecToValue(const V: TVarRec; out wasString: boolean): string;
// http://smartmobilestudio.com/forums/topic/is-array-of-const-supported-in-sms
begin
  wasString := not (V.VType in
    [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended,vtVariant]);
  with V do
  case VType of
  {$ifndef NEXTGEN}
  vtString:     result := string(VString^);
  vtAnsiString: result := string(AnsiString(VAnsiString));
  vtChar:       result := string(VChar);
  vtPChar:      result := string(VPChar);
  vtWideString: result := string(WideString(VWideString));
  {$endif}
  {$ifdef UNICODE}
  vtUnicodeString: result := string(VUnicodeString);
  {$endif}
  vtPWideChar:  result := string(VPWideChar);
  vtWideChar:   result := string(VWideChar);
  vtBoolean:    if VBoolean then result := '1' else result := '0';
  vtInteger:    result := IntToStr(VInteger);
  vtInt64:      result := IntToStr(VInt64^);
  {$ifdef FPC}
  vtQWord:      result := IntToStr(VQWord^);
  {$endif}
  vtCurrency:   DoubleToJSON(VCurrency^,result);
  vtExtended:   DoubleToJSON(VExtended^,result);
  vtObject:     result := ObjectToJSON(VObject);
  vtVariant: if TVarData(VVariant^).VType<=varNull then
    result := 'null' else begin
    wasString := VarIsStr(VVariant^);
    result := VVariant^;
  end;
  else result := '';
  end;
end;

procedure DoubleQuoteStr(var text: string);
var i,j: integer;
    tmp: string;
begin
  i := pos('"',text);
  if i=0 then begin
    text := '"'+text+'"';
    exit;
  end;
  tmp := '"'+copy(text,1,i)+'"';
  for j := i+1 to length(text) do
    if text[j]='"' then
      tmp := tmp+'""' else
      AppendChar(tmp,text[j]);
  text := tmp+'"';
end;


{ TJSONParser }

type
  /// the JSON node types, as recognized by TJSONParser
  TJSONParserKind = (
    kNone, kNull, kFalse, kTrue, kString, kInteger, kFloat, kObject, kArray);

  /// SAX parser for any JSON content
  {$ifdef USEOBJECTINSTEADOFRECORD}
  TJSONParser = object
  {$else}
  TJSONParser = record
  {$endif}
    JSON: string;
    Index: integer;
    JSONLength: integer;
    procedure Init(const aJSON: string; aIndex: integer);
    function GetNextChar: char;                {$ifdef HASINLINE}inline;{$endif}
    function GetNextNonWhiteChar: char;        {$ifdef HASINLINE}inline;{$endif}
    function CheckNextNonWhiteChar(aChar: char): boolean; {$ifdef HASINLINE}inline;{$endif}
    function GetNextString(out str: string): boolean; overload;
    function GetNextString: string; overload;  {$ifdef HASINLINE}inline;{$endif}
    function GetNextJSON(out Value: variant): TJSONParserKind;
    function CheckNextIdent(const ExpectedIdent: string): Boolean;
    function GetNextAlphaPropName(out fieldName: string): boolean;
    function ParseJSONObject(var Data: TJSONVariantData): boolean;
    function ParseJSONArray(var Data: TJSONVariantData): boolean;
    procedure AppendNextStringUnEscape(var str: string);
  end;

procedure TJSONParser.Init(const aJSON: string; aIndex: integer);
begin
  JSON := aJSON;
  JSONLength := length(JSON);
  Index := aIndex;
end;

function TJSONParser.GetNextChar: char;
begin
  if Index<=JSONLength then begin
    result := JSON[Index];
    inc(Index);
  end else
    result := #0;
end;

function TJSONParser.GetNextNonWhiteChar: char;
begin
  if Index<=JSONLength then
    repeat
      if JSON[Index]>' ' then begin
        result := JSON[Index];
        inc(Index);
        exit;
      end;
      inc(Index);
    until Index>JSONLength;
  result := #0;
end;

function TJSONParser.CheckNextNonWhiteChar(aChar: char): boolean;
begin
  if Index<=JSONLength then
    repeat
      if JSON[Index]>' ' then begin
        result := JSON[Index]=aChar;
        if result then
          inc(Index);
        exit;
      end;
      inc(Index);
    until Index>JSONLength;
  result := false;
end;

procedure TJSONParser.AppendNextStringUnEscape(var str: string);
var c: char;
    u: string;
    unicode,err: integer;
begin
  repeat
    c := GetNextChar;
    case c of
    #0: exit;
    '"': break;
    '\': begin
      c := GetNextChar;
      case c of
      #0: exit;
      'b': AppendChar(str,#08);
      't': AppendChar(str,#09);
      'n': AppendChar(str,#$0a);
      'f': AppendChar(str,#$0c);
      'r': AppendChar(str,#$0d);
      'u': begin
        u := Copy(JSON,Index,4);
        if length(u)<>4 then
          exit;
        inc(Index,4);
        val('$'+u,unicode,err);
        if err<>0 then
          exit;
        AppendChar(str,char(unicode));
      end;
      else AppendChar(str,c);
      end;
    end;
    else AppendChar(str,c);
    end;
  until false;
end;

function TJSONParser.GetNextString(out str: string): boolean;
var i: integer;
begin
  for i := Index to JSONLength do
    case JSON[i] of
    '"': begin // end of string without escape -> direct copy
      str := copy(JSON,Index,i-Index);
      Index := i+1;
      result := true;
      exit;
    end;
    '\': begin // need unescaping
      str := copy(JSON,Index,i-Index);
      Index := i;
      AppendNextStringUnEscape(str);
      result := true;
      exit;
    end;
    end;
  result := false;
end;

function TJSONParser.GetNextString: string;
begin
  if not GetNextString(result) then
    result := '';
end;

function TJSONParser.GetNextAlphaPropName(out fieldName: string): boolean;
var i: integer;
begin
  result := False;
  if (Index>=JSONLength) or
     not (Ord(JSON[Index]) in [Ord('A')..Ord('Z'),Ord('a')..Ord('z'),Ord('_'),Ord('$')]) then
    exit; // first char must be alphabetical
  for i := Index+1 to JSONLength do
    case Ord(JSON[i]) of
    Ord('0')..Ord('9'),Ord('A')..Ord('Z'),Ord('a')..Ord('z'),Ord('_'):
      ; // allow MongoDB extended syntax, e.g. {age:{$gt:18}}
    Ord(':'),Ord('='): begin // allow both age:18 and age=18 pairs
      fieldName := Copy(JSON,Index,i-Index);
      Index := i+1;
      result := true;
      exit;
    end;
    else exit;
    end;
end;

function TJSONParser.GetNextJSON(out Value: variant): TJSONParserKind;
var str: string;
    i64: Int64;
    d: double;
    start,err: integer;
begin
  result := kNone;
  case GetNextNonWhiteChar of
  'n': if copy(JSON,Index,3)='ull' then begin
         inc(Index,3);
         result := kNull;
         Value := null;
       end;
  'f': if copy(JSON,Index,4)='alse' then begin
         inc(Index,4);
         result := kFalse;
         Value := false;
       end;
  't': if copy(JSON,Index,3)='rue' then begin
         inc(Index,3);
         result := kTrue;
         Value := true;
       end;
  '"': if GetNextString(str) then begin
         result := kString;
         Value := str;
       end;
  '{': if ParseJSONObject(TJSONVariantData(Value)) then
         result := kObject;
  '[': if ParseJSONArray(TJSONVariantData(Value)) then
         result := kArray;
  '-','0'..'9': begin
    start := Index-1;
    while true do
      case JSON[Index] of
      '-','+','0'..'9','.','E','e': inc(Index);
      else break;
      end;
    str := copy(JSON,start,Index-start);
    val(str,i64,err);
    if err=0 then begin
      Value := i64;
      result := kInteger;
    end else begin
      val(str,d,err);
      if err<>0 then
        exit;
      Value := d;
      result := kFloat;
    end;
  end;
  end;
end;

function TJSONParser.CheckNextIdent(const ExpectedIdent: string): Boolean;
begin
  result := (GetNextNonWhiteChar='"') and
            (CompareText(GetNextString,ExpectedIdent)=0) and
            (GetNextNonWhiteChar=':');
end;

function TJSONParser.ParseJSONArray(var Data: TJSONVariantData): boolean;
var item: variant;
begin
  result := false;
  Data.Init;
  if not CheckNextNonWhiteChar(']') then // '[]' -> void array
    repeat
      if GetNextJSON(item)=kNone then
        exit;
      Data.AddValue(item);
      case GetNextNonWhiteChar of
      ',': continue;
      ']': break;
      else exit;
      end;
    until false;
  SetLength(Data.Values,Data.VCount);
  Data.VKind := jvArray;
  result := true;
end;

function TJSONParser.ParseJSONObject(var Data: TJSONVariantData): boolean;
var key: string;
    val: variant;
begin
  result := false;
  Data.Init;
  if not CheckNextNonWhiteChar('}') then // '{}' -> void object
    repeat
      if CheckNextNonWhiteChar('"') then begin
        if (not GetNextString(key)) or (GetNextNonWhiteChar<>':') then
          exit;
      end else
        if not GetNextAlphaPropName(key) then
          exit;                             
      if GetNextJSON(val)=kNone then
        exit; // writeln(Copy(JSON,Index-10,30));
      Data.AddNameValue(key,val);
      case GetNextNonWhiteChar of
      ',': continue;
      '}': break;
      else exit;
      end;
    until false;
  SetLength(Data.Names,Data.VCount);
  SetLength(Data.Values,Data.VCount);
  Data.VKind := jvObject;
  result := true;
end;


function JSONToValue(const JSON: string): variant;
var Parser: TJSONParser;
begin
  Parser.Init(JSON,1);
  Parser.GetNextJSON(result);
end;


{ RTTI-oriented functions }

const
  BASE64: array[0..63] of char =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var
  BASE64DECODE: array of ShortInt;

function BytesToBase64JSONString(const Bytes: TByteDynArray;
  withBase64Magic: boolean): string;
var i,len,x,c,j: cardinal;
    P: PChar;
begin
  len := length(Bytes);
  if len=0 then begin
    result := '';
    exit;
  end;
  if withBase64Magic then
    x := JSON_BASE64_MAGIC_LEN else
    x := 0;
  SetLength(result,((len+2)div 3)*4+x);
  P := pointer(result);
  if withBase64Magic then
    move(JSON_BASE64_MAGIC,P^,sizeof(JSON_BASE64_MAGIC));
  j := 0;
  for i := 1 to len div 3 do begin
    c := Bytes[j] shl 16 or Bytes[j+1] shl 8 or Bytes[j+2];
    inc(j,3);
    P[x]   := BASE64[(c shr 18) and $3f];
    P[x+1] := BASE64[(c shr 12) and $3f];
    P[x+2] := BASE64[(c shr 6) and $3f];
    P[x+3] := BASE64[c and $3f];
    inc(x,4);
  end;
  case len mod 3 of
    1: begin
      c := Bytes[j] shl 4;
      P[x]   := BASE64[(c shr 6) and $3f];
      P[x+1] := BASE64[c and $3f];
      P[x+2] := '=';
      P[x+3] := '=';
      inc(x,4);
    end;
    2: begin
      c := Bytes[j] shl 10 or Bytes[j+1] shl 2;
      P[x]   := BASE64[(c shr 12) and $3f];
      P[x+1] := BASE64[(c shr 6) and $3f];
      P[x+2] := BASE64[c and $3f];
      P[x+3] := '=';
      inc(x,4);
    end;
  end;
  assert(integer(x)=Length(Result));
end;

function Base64One(c: Char): integer;
  {$ifdef HASINLINE}inline;{$endif}
begin
  result := ord(c);
  if result>127 then
    result := -1 else
    result := BASE64DECODE[result];
end;

function Base64JSONStringToBytes(const JSONString: string;
  var Bytes: TByteDynArray; withBase64Magic: boolean): boolean;
var i,bits,value,x,magiclen,len: cardinal;
begin
  result := JSONString='';
  if result then
    exit;
  if withBase64Magic then
    if comparemem(pointer(JSONString),@JSON_BASE64_MAGIC,sizeof(JSON_BASE64_MAGIC)) then
      magiclen := JSON_BASE64_MAGIC_LEN else
      {$ifndef UNICODE}
      if JSONString[1]='?' then // handle UTF-8 decoding error on ANSI Delphi
        magiclen := 1 else
      {$endif}
      exit else
    magiclen := 0; // withBase64Magic=false
  x := length(JSONString);
  len := x-magiclen;
  if len and 3<>0 then
    exit;
  if len=0 then
    Bytes := nil else begin
    if BASE64DECODE=nil then begin
      SetLength(BASE64DECODE,128);
      for i := 0 to 127 do
        BASE64DECODE[i] := -1;
      for i := 0 to high(BASE64) do
        BASE64DECODE[ord(BASE64[i])] := i;
    end;
    len := (len shr 2)*3;
    if Base64One(JSONString[x])<0 then begin
      dec(len);
      if Base64One(JSONString[x-1])<0 then
        dec(len);
    end;
    SetLength(Bytes,len);
    bits := 0;
    value := 0;
    len := 0;
    for i := magiclen+1 to Length(JSONString) do begin
      x := ord(JSONString[i]); // inlined Base64One(JSONString[i])
      if x>127 then
        break;
      x := cardinal(BASE64DECODE[x]);
      if integer(x)<0 then
        break;
      value := value*64+x;
      bits := bits+6;
      if bits>=8 then begin
        bits := bits-8;
        x := value shr bits;
        value := value and ((1 shl bits)-1);
        Bytes[len] := x;
        inc(len);
      end;
    end;
  end;
  result := len=cardinal(length(Bytes));
end;

function RTTIPropInfoTypeName(PropInfo: TRTTIPropInfo): string;
begin
  result := ShortStringToString(@PropInfo^.PropType^.Name);
end;

procedure GetPropsInfo(TypeInfo: TRTTITypeInfo; var PropNames: TStringDynArray;
  var PropRTTI: TRTTIPropInfoDynArray);
var i,n: integer;
    List: PPropList;
begin
  n := GetPropList(PTypeInfo(TypeInfo),List);
  SetLength(PropNames,n);
  SetLength(PropRTTI,n);
  for i := 0 to n-1 do begin
    PropRTTI[i] := List^[i];
    PropNames[i] := ShortStringToString(@PropRTTI[i]^.Name);
  end;
  freemem(List);
end;

function IsDateTime(PropInfo: TRTTIPropInfo): boolean;
  {$ifdef HASINLINE}inline;{$endif}
begin
  result := PropInfo^.PropType{$ifndef FPC}^{$endif}=TypeInfo(TDateTime);
end;

{$ifndef FPC}
type
  // used to map a TPropInfo.GetProc/SetProc and retrieve its kind on Delphi
  PropWrap = packed record
    FillBytes: array [0..SizeOf(Pointer)-2] of byte;
    /// = $ff for a field address, or =$fe for a virtual method
    Kind: byte;
  end;
{$endif FPC}

function IsBlob(PropInfo: TRTTIPropInfo): boolean;
  {$ifdef HASINLINE}inline;{$endif}
begin // we only handle plain TByteDynArray properties without getter/setter
{$ifdef FPC}
  result := (PropInfo^.PropType=TypeInfo(TByteDynArray)) and
            (PropInfo^.PropProcs and 3=ptField);
{$else}
  result := (PropInfo^.PropType^=TypeInfo(TByteDynArray)) and
            (PropWrap(PropInfo^.GetProc).Kind=$FF);
{$endif}
end;

function GetTByteDynArrayProp(Instance: TObject; PropInfo: TRTTIPropInfo): PByteDynArray;
  {$ifdef HASINLINE}inline;{$endif}
begin
  result := Pointer(NativeUInt(Instance)+
    (NativeUInt(PropInfo^.GetProc){$ifndef FPC} and $00FFFFFF{$endif}));
end;

function GetInstanceProp(Instance: TObject; PropInfo: TRTTIPropInfo; StoreClassName: boolean): variant;
var obj: TObject;
begin
  VarClear(result);
  if (PropInfo=nil) or (Instance=nil) then
    exit;
  case PropInfo^.PropType^.Kind of
  tkInt64{$ifdef FPC}, tkQWord{$endif}:
    result := GetInt64Prop(Instance,PropInfo);
  tkEnumeration, tkInteger, tkSet:
    result := GetOrdProp(Instance,PropInfo);
  {$ifdef NEXTGEN}
  tkUString:
    result := GetStrProp(Instance,PropInfo);
  {$else}
  {$ifdef FPC}tkAString,{$endif}tkLString:
    result := GetStrProp(Instance,PropInfo);
  tkWString:
    result := GetWideStrProp(Instance,PropInfo);
  {$ifdef UNICODE}
  tkUString:
    result := GetUnicodeStrProp(Instance,PropInfo);
  {$endif UNICODE}
  {$endif NEXTGEN}
  tkFloat:
    if IsDateTime(PropInfo) then
      result := DateTimeToIso8601(GetFloatProp(Instance,PropInfo)) else
      result := GetFloatProp(Instance,PropInfo);
  tkVariant:
    result := GetVariantProp(Instance,PropInfo);
  tkClass: begin
    obj := TObject(NativeInt(GetOrdProp(Instance,PropInfo)));
    if obj=nil then
      result := null else
      TJSONVariantData(result).Init(ObjectToJSON(obj, StoreClassName));
  end;
  tkDynArray:
    if IsBlob(PropInfo) then
      result := BytesToBase64JSONString(GetTByteDynArrayProp(Instance,PropInfo)^);
  end;
end;

procedure SetInstanceProp(Instance: TObject; PropInfo: TRTTIPropInfo;
  const Value: variant);
var blob: PByteDynArray;
    obj: TObject;
begin
  if (PropInfo<>nil) and (Instance<>nil) then
  case PropInfo^.PropType^.Kind of
  tkInt64{$ifdef FPC}, tkQWord{$endif}:
    if TVarData(Value).VType=varInt64 then
      SetInt64Prop(Instance,PropInfo,TVarData(Value).VInt64) else
      SetOrdProp(Instance,PropInfo,Value);
  tkEnumeration, tkInteger, tkSet:
    SetOrdProp(Instance,PropInfo,Value);
  {$ifdef NEXTGEN}
  tkUString:
    if TVarData(Value).VType<=varNull then
      SetStrProp(Instance,PropInfo,'') else
      SetStrProp(Instance,PropInfo,Value);
  {$else}
  {$ifdef FPC}tkAString,{$endif} tkLString:
    if TVarData(Value).VType<=varNull then
      SetStrProp(Instance,PropInfo,'') else
      SetStrProp(Instance,PropInfo,Value);
  tkWString:
    if TVarData(Value).VType<=varNull then
      SetWideStrProp(Instance,PropInfo,'') else
      SetWideStrProp(Instance,PropInfo,Value);
  {$ifdef UNICODE}
  tkUString:
    if TVarData(Value).VType<=varNull then
      SetUnicodeStrProp(Instance,PropInfo,'') else
      SetUnicodeStrProp(Instance,PropInfo,Value);
  {$endif UNICODE}
  {$endif NEXTGEN}
  tkFloat:
    if IsDateTime(PropInfo) and VarIsStr(Value) then
      SetFloatProp(Instance,PropInfo,Iso8601ToDateTime(Value)) else
      SetFloatProp(Instance,PropInfo,Value);
  tkVariant:
    SetVariantProp(Instance,PropInfo,Value);
  tkDynArray:
    if IsBlob(PropInfo) then begin
      blob := GetTByteDynArrayProp(Instance,PropInfo);
      if (TVarData(Value).VType<=varNull) or
        not Base64JSONStringToBytes(Value,blob^) then
        Finalize(blob^);
    end;
  tkClass: begin
    obj := TObject(NativeInt(GetOrdProp(Instance,PropInfo)));
    if TVarData(Value).VType>varNull then
      if obj=nil then begin
        obj := JSONVariantData(Value)^.ToNewObject;
        if obj<>nil then
          SetOrdProp(Instance,PropInfo,NativeInt(obj));
      end else
        JSONVariantData(Value)^.ToObject(obj);
  end;
  end;
end;

function JSONToObjectList(ItemClass: TClass; const JSON: string): TObjectList;
var doc: TJSONVariantData;
    item: TObject;
    i: integer;
begin
  doc.Init(JSON);
  if (doc.VKind<>jvArray) or (ItemClass=nil) then
    result := nil else begin
    result := TObjectList.Create;
    for i := 0 to doc.Count-1 do begin
      item := ItemClass.Create;
      if not JSONVariantData(doc.Values[i])^.ToObject(item) then begin
        FreeAndNil(result);
        exit;
      end;
      result.Add(item);
    end;
  end;
end;

function JSONToObject(Instance: TObject; const JSON: string): boolean;
var doc: TJSONVariantData;
begin
  if Instance=nil then
    result := false else begin
    doc.Init(JSON);
    result := doc.ToObject(Instance);
  end;
end;

function JSONToNewObject(const JSON: string): pointer;
var doc: TJSONVariantData;
begin
  doc.Init(JSON);
  result := doc.ToNewObject;
end;

var
  RegisteredClass: array of record
    ClassName: string;
    ClassType: TClass;
  end;

function FindClassForJSON(const ClassName: string): integer;
begin
  for result := 0 to high(RegisteredClass) do
    if IdemPropName(RegisteredClass[result].ClassName,ClassName) then
      exit;
  result := -1;
end;

function CreateClassForJSON(const ClassName: string): TObject;
var i: integer;
begin
  i := FindClassForJSON(ClassName);
  if i<0 then
    result := nil else
    result := RegisteredClass[i].ClassType.Create;
end;

procedure RegisterClassForJSON(const Classes: array of TClass);
var c,i: integer;
    name: string;
begin
  for c := 0 to high(Classes) do begin
    name := string(Classes[c].ClassName);
    i := FindClassForJSON(name);
    if i>=0 then
      continue;
    i := length(RegisteredClass);
    SetLength(RegisteredClass,i+1);
    RegisteredClass[i].ClassName := Name;
    RegisteredClass[i].ClassType := Classes[c];
  end;
end;

function ObjectToJSON(Instance: TObject; StoreClassName: boolean): string;
var TypeInfo: PTypeInfo;
    PropCount, i: integer;
    PropList: PPropList;
    PropInfo: PPropInfo;
begin
  if Instance=nil then begin
    result := 'null';
    exit;
  end;
  {$ifndef NEXTGEN}
  if Instance.InheritsFrom(TList) then begin
    if TList(Instance).Count=0 then
      result := '[]' else begin
      result := '[';
      for i := 0 to TList(Instance).Count-1 do
        result := result+ObjectToJSON(TObject(
          TList(Instance).List{$ifdef FPC}^{$endif}[i]),StoreClassName)+',';
      result[length(result)] := ']';
    end;
    exit;
  end;
  {$endif}
  if Instance.InheritsFrom(TStrings) then begin
    if TStrings(Instance).Count=0 then
      result := '[]' else begin
      result := '[';
      for i := 0 to TStrings(Instance).Count-1 do
        result := result+StringToJSON(TStrings(Instance).Strings[i])+',';
      result[length(result)] := ']';
    end;
    exit;
  end;
  if Instance.InheritsFrom(TCollection) then begin
    if TCollection(Instance).Count=0 then
      result := '[]' else begin
      result := '[';
      for i := 0 to TCollection(Instance).Count-1 do
        result := result+ObjectToJSON(TCollection(Instance).Items[i],StoreClassName)+',';
      result[length(result)] := ']';
    end;
    exit;
  end;
  TypeInfo := Instance.ClassInfo;
  if TypeInfo=nil then begin
    result := 'null';
    exit;
  end;
  PropCount := GetPropList(TypeInfo,PropList);
  if PropCount>0 then
    try
      if StoreClassName then
        result := '{"ClassName":"'+string(Instance.ClassName)+'",' else
        result := '{';
      for i := 0 to PropCount-1 do begin
        PropInfo := PropList^[i];
        result := result+StringToJSON(ShortStringToString(@PropInfo^.Name))+':'+
          ValueToJSON(GetInstanceProp(Instance,PropInfo,StoreClassName))+',';
      end;
      result[length(result)] := '}';
    finally
      FreeMem(PropList);
    end else
    result := 'null';
end;

procedure GetPublishedMethods(Instance: TObject; out Methods: TPublishedMethodDynArray);
var n: integer;
  procedure AddParentsFirst(C: TClass);
  type
    TMethodInfo = packed record
      {$ifdef FPC}
      Name: PShortString;
      Addr: Pointer;
      {$else}
      Len: Word;
      Addr: Pointer;
      Name: Byte;
      {$endif}
    end;
  var M: ^TMethodInfo;
      Method: TMethod;
      i,MCount: integer;
  begin
    if C=nil then
      exit;
    AddParentsFirst(C.ClassParent); // put children methods afterward
    M := PPointer(NativeInt(C)+vmtMethodTable)^;
    if M=nil then
      exit;
    Method.Data := Instance;
    MCount := {$ifdef FPC}PCardinal{$else}PWord{$endif}(M)^;
    inc({$ifdef FPC}PCardinal{$else}PWord{$endif}(M));
    for i := 1 to MCount do begin
      Method.Code := M^.Addr;
      if n>=length(Methods) then
        SetLength(Methods,n+32);
      Methods[n].Name := {$ifdef FPC}M^.Name^{$else}ShortStringToString(@M^.Name){$endif};
      Methods[n].Method := Method;
      inc(n);
      {$ifdef FPC}
      inc(M);
      {$else}
      inc(PByte(M),M^.Len);
      {$endif}
    end;
  end;
begin
  if Instance=nil then
    exit;
  n := 0;
  AddParentsFirst(Instance.ClassType);
  SetLength(Methods,n);
end;


{ TJSONVariantData }

procedure TJSONVariantData.Init;
begin
  VType := JSONVariantType.VarType;
  {$ifdef UNICODE} // makes compiler happy
  _Align := 0;
  {$endif}
  VKind := jvUndefined;
  VCount := 0;
  pointer(Names) := nil;
  pointer(Values) := nil;
end;

procedure TJSONVariantData.Init(const JSON: string);
begin
  Init;
  FromJSON(JSON);
  if VType=varNull then
    VKind := jvObject else
  if VType<>JSONVariantType.VarType then
    Init; // we expect a true JSON array or object here
end;

procedure TJSONVariantData.InitFrom(const aValues: TVariantDynArray);
begin
  Init;
  VKind := jvArray;
  Values := aValues;
  VCount := Length(aValues);
end;

procedure TJSONVariantData.Clear;
begin
  Names := nil;
  Values := nil;
  Init;
end;

procedure TJSONVariantData.AddNameValue(const aName: string;
  const aValue: variant);
begin
  if VKind=jvUndefined then
    VKind := jvObject else
    if VKind<>jvObject then
      raise EJSONException.CreateFmt('AddNameValue(%s) over array',[aName]);
  if VCount=length(Values) then begin
    SetLength(Values,VCount+VCount shr 3+32);
    SetLength(Names,VCount+VCount shr 3+32);
  end;
  Values[VCount] := aValue;
  Names[VCount] := aName;
  inc(VCount);
end;

procedure TJSONVariantData.AddValue(const aValue: variant);
begin
  if VKind=jvUndefined then
    VKind := jvArray else
    if VKind<>jvArray then
      raise EJSONException.Create('AddValue() over object');
  if VCount=length(Values) then
    SetLength(Values,VCount+VCount shr 3+32);
  Values[VCount] := aValue;
  inc(VCount);
end;

function TJSONVariantData.FromJSON(const JSON: string): boolean;
var Parser: TJSONParser;
begin
  Parser.Init(JSON,1);
  result := Parser.GetNextJSON(variant(self)) in [kObject,kArray];
end;

function TJSONVariantData.Data(const aName: string): PJSONVariantData;
var i: integer;
begin
  i := NameIndex(aName);
  if (i<0) or (TVarData(Values[i]).VType<>JSONVariantType.VarType) then
    result := nil else
    result := @Values[i];
end;

function TJSONVariantData.GetKind: TJSONVariantKind;
begin
  if (@self=nil) or (VType<>JSONVariantType.VarType) then
    result := jvUndefined else
    result := VKind;
end;

function TJSONVariantData.GetCount: integer;
begin
  if (@self=nil) or (VType<>JSONVariantType.VarType) then
    result := 0 else
    result := VCount;
end;

function TJSONVariantData.GetValue(const aName: string): variant;
begin
  VarClear(result);
  if (@self<>nil) and (VType=JSONVariantType.VarType) and (VKind=jvObject) then
    GetVarData(aName,TVarData(result));
end;

function TJSONVariantData.GetValueCopy(const aName: string): variant;
var i: cardinal;
begin
  VarClear(result);
  if (@self<>nil) and (VType=JSONVariantType.VarType) and (VKind=jvObject) then begin
    i := cardinal(NameIndex(aName));
    if i<cardinal(length(Values)) then
      result := Values[i];
  end;
end;

function TJSONVariantData.GetItem(aIndex: integer): variant;
begin
  VarClear(result);
  if (@self<>nil) and (VType=JSONVariantType.VarType) and (VKind=jvArray) then
    if cardinal(aIndex)<cardinal(VCount) then
      result := Values[aIndex];
end;

procedure TJSONVariantData.SetItem(aIndex: integer; const aItem: variant);
begin
  if (@self<>nil) and (VType=JSONVariantType.VarType) and (VKind=jvArray) then
    if cardinal(aIndex)<cardinal(VCount) then
      Values[aIndex] := aItem;
end;

function TJSONVariantData.GetVarData(const aName: string;
  var Dest: TVarData): boolean;
var i: cardinal;
begin
  i := cardinal(NameIndex(aName));
  if i<cardinal(length(Values)) then begin
    Dest.VType := varVariant or varByRef;
    Dest.VPointer := @Values[i];
    result := true;
  end else
    result := false;
end;

function TJSONVariantData.NameIndex(const aName: string): integer;
begin
  if (@self<>nil) and (VType=JSONVariantType.VarType) and (Names<>nil) then
    for result := 0 to VCount-1 do
      if Names[result]=aName then
        exit;
  result := -1;
end;

procedure TJSONVariantData.SetPath(const aPath: string; const aValue: variant);
var i: integer;
begin
  for i := length(aPath) downto 1 do 
    if aPath[i]='.' then begin
      EnsureData(copy(aPath,1,i-1))^.SetValue(copy(aPath,i+1,maxInt),aValue);
      exit;
    end;
  SetValue(aPath,aValue);
end;

function TJSONVariantData.EnsureData(const aPath: string): PJSONVariantData;
var i: integer;
    new: TJSONVariantData;
begin // recursive value set
  i := Pos('.',aPath);
  if i=0 then begin
    i := NameIndex(aPath);
    if i<0 then begin // not existing: create new
      new.Init;
      AddNameValue(aPath,variant(new));
      result := @Values[VCount-1];
    end else begin
      if TVarData(Values[i]).VType<>JSONVariantType.VarType then begin
        VarClear(Values[i]);
        TJSONVariantData(Values[i]).Init; // create as TJSONVariantData
      end;
      result := @Values[i];
    end;
  end else
    result := EnsureData(copy(aPath,1,i-1))^.EnsureData(copy(aPath,i+1,maxInt));
end;

function TJSONVariantData.AddItem: PJSONVariantData;
var new: TJSONVariantData;
begin
  new.Init;
  AddValue(variant(new));
  result := @Values[VCount-1];
end;

procedure TJSONVariantData.SetValue(const aName: string;
  const aValue: variant);
var i: integer;
begin
  if @self=nil then
    raise EJSONException.Create('Unexpected Value[] access');
  if aName='' then
    raise EJSONException.Create('Unexpected Value['''']');
  i := NameIndex(aName);
  if i<0 then
    AddNameValue(aName,aValue) else
    Values[i] := aValue;
end;

function TJSONVariantData.ToJSON: string;
var i: integer;
begin
  case VKind of
  jvObject:
    if VCount=0 then
      result := '{}' else begin
      result := '{';
      for i := 0 to VCount-1 do
        result := result+StringToJSON(Names[i])+':'+ValueToJSON(Values[i])+',';
      result[length(result)] := '}';
  end;
  jvArray:
    if VCount=0 then
      result := '[]' else begin
      result := '[';
      for i := 0 to VCount-1 do
        result := result+ValueToJSON(Values[i])+',';
      result[length(result)] := ']';
    end;
  else result := 'null';
  end;
end;

function TJSONVariantData.ToNewObject: TObject;
var ndx,i: Integer;
begin
  result := nil;
  if (Kind<>jvObject) or (Count=0) then
    exit;
  ndx := NameIndex('ClassName');
  if ndx<0 then
    exit;
  result := CreateClassForJSON(Values[ndx]);
  if result=nil then
    exit; // class name has not been registered
  for i := 0 to Count-1 do
    if i<>ndx then
      SetInstanceProp(result,GetPropInfo(result,Names[i]),Values[i]);
end;

function TJSONVariantData.ToObject(Instance: TObject): boolean;
var i: integer;
    aItem: TCollectionItem;
begin
  result := false;
  if Instance=nil then
    exit;
  case Kind of
  jvObject:
    for i := 0 to Count-1 do
      SetInstanceProp(Instance,
        GetPropInfo(Instance,Names[i]),Values[i]);
  jvArray:
    if Instance.InheritsFrom(TCollection) then begin
      TCollection(Instance).Clear;
      for i := 0 to Count-1 do begin
        aItem := TCollection(Instance).Add;
        if not JSONVariantData(Values[i])^.ToObject(aItem) then
          exit;
      end;
    end else
    if Instance.InheritsFrom(TStrings) then
    try
      TStrings(Instance).BeginUpdate;
      TStrings(Instance).Clear;
      for i := 0 to Count-1 do
        TStrings(Instance).Add(Values[i]);
    finally
      TStrings(Instance).EndUpdate;
    end else
      exit;
  else
    exit;
  end;
  result := true;
end;


{ TJSONVariant }

procedure TJSONVariant.Cast(var Dest: TVarData; const Source: TVarData);
begin
  CastTo(Dest,Source,VarType);
end;

procedure TJSONVariant.CastTo(var Dest: TVarData; const Source: TVarData;
  const AVarType: TVarType);
begin
  if Source.VType<>VarType then
    RaiseCastError;
  variant(Dest) := TJSONVariantData(Source).ToJSON;
end;

procedure TJSONVariant.Clear(var V: TVarData);
begin
  V.VType := varEmpty;
  Finalize(TJSONVariantData(V).Names);
  Finalize(TJSONVariantData(V).Values);
end;

procedure TJSONVariant.Copy(var Dest: TVarData; const Source: TVarData;
  const Indirect: Boolean);
begin
  if Indirect then
    SimplisticCopy(Dest,Source,true) else begin
    VarClear(variant(Dest));
    TJSONVariantData(Dest).Init;
    TJSONVariantData(Dest) := TJSONVariantData(Source);
  end;
end;

{$ifndef FPC}
{$ifndef ISDELPHI6}
function TJSONVariant.FixupIdent(const AText: string): string;
begin // we expect the names to be case-sensitive
  result := AText;
end;
{$endif}
{$endif}

function TJSONVariant.GetProperty(var Dest: TVarData; const V: TVarData;
  const Name: string): Boolean;
begin
  if not TJSONVariantData(V).GetVarData(Name,Dest) then
    Dest.VType := varNull;
  result := true;
end;

{$ifdef FPC_VARIANTSETVAR}
function TJSONVariant.SetProperty(var V: TVarData; const Name: string;
  const Value: TVarData): Boolean;
{$else}
function TJSONVariant.SetProperty(const V: TVarData; const Name: string;
  const Value: TVarData): Boolean;
{$endif}
begin
  {$ifdef FPC}
  {$ifndef FPC_VARIANTSETVAR} 
  raise EJSONException.Create('Setting TJSONVariant via late-binding does not'+
    ' work with FPC - see http://mantis.freepascal.org/view.php?id=26773 -'+
    ' use latest SVN or JSONVariantDataSafe(jsonvar)^[''prop''] := ... instead');
  {$endif}
  {$endif}
  TJSONVariantData(V).SetValue(Name,variant(Value));
  result := true;
end;


{ TJSONTable }

constructor TJSONTable.Create(const aJSON: string);
var f,firstValue: integer;
    EndOfField: char;
    fieldCount, fieldName, dummy: variant;
    Parser: TJSONParser;
begin
  Parser.Init(aJSON,1);
  fJSON := aJSON;
  EndOfField := #0;
  if (Parser.GetNextNonWhiteChar='{') and
      Parser.CheckNextIdent('fieldCount') and
     (Parser.GetNextJSON(fieldCount)=kInteger) and
     (Parser.GetNextNonWhiteChar=',') and
      Parser.CheckNextIdent('values') and
     (Parser.GetNextNonWhiteChar='[') then begin
    // non expanded format: {"fieldCount":2,"values":["ID","Int",1,0,2,0,3,...]
    SetLength(fFieldNames,integer(fieldCount));
    for f := 0 to high(fFieldNames) do begin
      if Parser.GetNextJSON(fieldName)<>kString then
        exit;
      fFieldNames[f] := fieldName;
      EndOfField := Parser.GetNextNonWhiteChar;
      if EndOfField<>',' then
        if (EndOfField<>']') or (f<>High(FieldNames)) then
          exit
    end;
    if EndOfField=',' then
      fJSONIndexFirstValue := Parser.Index;
  end else begin
    // expanded format: [{"ID":1,"Int":0},{"ID":2,"Int":0},{"ID":3,...]
    Parser.Index := 1;
    if (Parser.GetNextNonWhiteChar='[') and
       (Parser.GetNextNonWhiteChar='{') then begin
      firstValue := Parser.Index;
      f := 0;
      repeat
        if (Parser.GetNextJSON(fieldName)<>kString) or
           (Parser.GetNextNonWhiteChar<>':') then
          exit;
        if Parser.GetNextJSON(dummy)=kNone then
          exit;
        SetLength(fFieldNames,f+1);
        fFieldNames[f] := fieldName;
        inc(f);
        EndOfField := Parser.GetNextNonWhiteChar;
        if EndOfField<>',' then
          if EndOfField='}' then
            break else
            exit;
      until false;
      fJSONIndexFirstValue := firstValue;
      fJSONExpanded := true;
    end;    
  end;
  SetLength(fRowValues,length(fFieldNames));
end;

function TJSONTable.FieldIndex(const FieldName: string): integer;
begin
  for result := 0 to high(fFieldNames) do
    if CompareText(fFieldNames[result],FieldName)=0 then
      exit;
  result := -1;
end;

function TJSONTable.Get(const FieldName: string): variant;
var ndx: integer;
begin
  ndx := FieldIndex(FieldName);
  if ndx<0 then
    result := null else
    result := fRowValues[ndx];
end;

function TJSONTable.Step(SeekFirst: boolean): boolean;
var f: integer;
    EndOfField: char;
    Parser: TJSONParser;
begin
  result := false;
  if SeekFirst or (fJSONCurrentIndex=0) then
    fJSONCurrentIndex := fJSONIndexFirstValue;
  if fJSONCurrentIndex<=0 then
    exit;
  Parser.Init(fJSON,fJSONCurrentIndex);
  fJSONCurrentIndex := -1; // indicates end of content in case of exit below
  EndOfField := #0;
  for f := 0 to high(fRowValues) do begin
    if fJSONExpanded and not Parser.CheckNextIdent(fFieldNames[f]) then
      exit;
    if Parser.GetNextJSON(fRowValues[f])=kNone then
      exit;
    EndOfField := Parser.GetNextNonWhiteChar;
    if EndOfField<>',' then
      if f<>High(fRowValues) then
        exit else
      if ((EndOfField=']') and (not fJSONExpanded)) or
         ((EndOfField='}') and fJSONExpanded) then
        break else
        exit;
  end;
  if fJSONExpanded then begin
    if EndOfField<>'}' then
      exit;
    EndOfField := Parser.GetNextNonWhiteChar;
    if (EndOfField=',') and
       (Parser.GetNextNonWhiteChar<>'{') then
      exit;
  end;
  if EndOfField=',' then
    fJSONCurrentIndex := Parser.Index; // indicates next Step() has data
  result := true;
end;

function TJSONTable.StepValue(var RowValues: variant; SeekFirst: boolean): boolean;
begin
  result := Step(SeekFirst);
  if not result then
    exit;
  if TVarData(RowValues).VType<>JSONVariantType.VarType then begin
    VarClear(RowValues);
    TJSONVariantData(RowValues).Init;
  end;
  TJSONVariantData(RowValues).VKind := jvObject;
  TJSONVariantData(RowValues).VCount := Length(fFieldNames);
  TJSONVariantData(RowValues).Names := fFieldNames;
  TJSONVariantData(RowValues).Values := fRowValues;
end;


{ TJSONTableObject }

function TJSONTableObject.StepObject(Instance: TObject; SeekFirst: boolean=false): boolean;
begin
  if (Instance=nil) then
    result := false else
    result := Step(SeekFirst);
  if result then
    FillInstance(Instance);
end;

procedure TJSONTableObject.FillInstance(Instance: TObject);
var i: integer;
begin
  if fTypeInfo<>Instance.ClassInfo then
    FillPropInfo(Instance.ClassInfo);
  for i := 0 to Length(fPropInfo)-1 do
    SetInstanceProp(Instance,fPropInfo[i],fRowValues[i]);
end;

function TJSONTableObject.GetPropInfo(aTypeInfo: TRTTITypeInfo;
  const PropName: string): TRTTIPropInfo;
begin
  result := TypInfo.GetPropInfo(PTypeInfo(aTypeInfo),PropName);
end;

procedure TJSONTableObject.FillPropInfo(aTypeInfo: TRTTITypeInfo);
var i: integer;
begin
  fTypeInfo := aTypeInfo;
  SetLength(fPropInfo,Length(fFieldNames));
  for i := 0 to length(FieldNames)-1 do
    fPropInfo[i] := GetPropInfo(aTypeInfo,fFieldNames[i]);
end;


initialization
  JSONVariantType := TJSONVariant.Create;
  {$ifndef FPC}
  {$ifndef NOFORMATSETTINGS}
  {$ifdef ISDELPHIXE}
  SettingsUS := TFormatSettings.Create('en_US');
  {$else}
  GetLocaleFormatSettings($0409,SettingsUS);
  {$endif}
  {$endif}
  {$endif}
end.
