git-svn-id: https://192.168.0.254/svn/Componentes.Terceros.RemObjects@46 b6239004-a887-0f4b-9937-50029ccdca16
1157 lines
30 KiB
ObjectPascal
1157 lines
30 KiB
ObjectPascal
unit uROJSONParser;
|
|
|
|
{$I RemObjects.inc}
|
|
// code chars > 127 as \uXXXX
|
|
{$DEFINE WRITE_ASCII_COMPATIBLE}
|
|
{$DEFINE RaiseInvalidDataTypeException}
|
|
interface
|
|
uses
|
|
SysUtils, Classes;
|
|
|
|
type
|
|
{$IFDEF UNICODE}
|
|
JSON_String = string;
|
|
JSON_Char = Char;
|
|
JSON_PChar = PChar;
|
|
{$ELSE}
|
|
JSON_String = Widestring;
|
|
JSON_Char = WideChar;
|
|
JSON_PChar = PWideChar;
|
|
{$ENDIF}
|
|
|
|
TROJSONDataType = (jdtNull, jdtString, jdtNumber, jdtObject, jdtArray, jdtBoolean);
|
|
|
|
TROJSONObject = class;
|
|
TROJSONArray = class;
|
|
|
|
TROJSONValue = class(TCollectionItem)
|
|
private
|
|
FValueType: TROJSONDataType;
|
|
FValue: Variant;
|
|
FObject: TCollection;
|
|
function GetAsObject: TROJSONObject;
|
|
function GetAsArray: TROJSONArray;
|
|
function GetAsBoolean: Boolean;
|
|
function GetAsNumber: Variant;
|
|
function GetAsString: JSON_String;
|
|
procedure SetAsObject(const Value: TROJSONObject);
|
|
procedure SetAsArray(const Value: TROJSONArray);
|
|
procedure SetAsString(const Value: JSON_String);
|
|
procedure SetAsBoolean(const Value: Boolean);
|
|
procedure SetAsNumber(const Value: Variant);
|
|
function GetVarValue: Variant;
|
|
procedure SetVarValue(const Value: Variant);
|
|
protected
|
|
Procedure IntSaveToStream(AStream: TStream);virtual;
|
|
Procedure IntLoadFromStream(AStream: TStream);virtual;
|
|
public
|
|
procedure Clear;
|
|
constructor Create(Collection: TCollection); overload;override;
|
|
constructor Create(aType: TROJSONDataType); reintroduce; overload;
|
|
destructor Destroy; override;
|
|
Procedure SaveToStream(AStream: TStream; AUTF8Stream: boolean = True);
|
|
Procedure LoadFromStream(AStream: TStream; AUTF8Stream: boolean = True);
|
|
property ValueType: TROJSONDataType read FValueType;
|
|
property AsObject: TROJSONObject read GetAsObject write SetAsObject;
|
|
property ASArray: TROJSONArray read GetAsArray write SetAsArray;
|
|
property AsString: JSON_String read GetAsString write SetAsString;
|
|
property AsBoolean: Boolean read GetAsBoolean Write SetAsBoolean;
|
|
Property AsNumber: Variant read GetAsNumber write SetAsNumber;
|
|
procedure SetAsNull;
|
|
property VarValue: Variant read GetVarValue write SetVarValue;
|
|
end;
|
|
|
|
TROJSONProperty = class(TROJSONValue)
|
|
private
|
|
FName: JSON_String;
|
|
procedure SetName(const Value: JSON_String);
|
|
protected
|
|
Procedure IntSaveToStream(AStream: TStream);override;
|
|
Procedure IntLoadFromStream(AStream: TStream);override;
|
|
public
|
|
property Name: JSON_String read FName write SetName;
|
|
end;
|
|
|
|
TROJSONObject = class(TCollection)
|
|
private
|
|
protected
|
|
function GetItems(Index: Integer): TROJSONProperty;
|
|
procedure SetItems(Index: Integer; const Value: TROJSONProperty);
|
|
Procedure IntSaveToStream(AStream: TStream);
|
|
Procedure IntLoadFromStream(AStream: TStream);
|
|
public
|
|
constructor Create;
|
|
Procedure SaveToStream(AStream: TStream; AUTF8Stream: boolean = True);
|
|
Procedure LoadFromStream(AStream: TStream; AUTF8Stream: boolean = True);
|
|
property Items[Index: Integer] :TROJSONProperty read GetItems write SetItems; default;
|
|
function FindItem(const AName: JSON_String):TROJSONProperty;
|
|
function ItemByName(const AName: JSON_String):TROJSONProperty;
|
|
function IndexOf(const AName: JSON_String):integer;
|
|
function Add: TROJSONProperty;
|
|
function AddStringProperty(const AName, aValue: JSON_String): TROJSONProperty;
|
|
function AddArrayProperty(const AName: JSON_String): TROJSONProperty;
|
|
function AddObjectProperty(const AName: JSON_String): TROJSONProperty;
|
|
function AddNullProperty(const AName: JSON_String): TROJSONProperty;
|
|
function AddBooleanProperty(const AName: JSON_String; const AValue: Boolean): TROJSONProperty;
|
|
function AddNumberProperty(const AName: JSON_String; const AValue: Variant): TROJSONProperty;
|
|
function AddVariantProperty(const AName: JSON_String; const AValue: Variant): TROJSONProperty;
|
|
function GetArrayItemByName(const AName: JSON_String; ACreate: Boolean): TROJSONArray;
|
|
function GetObjectItemByName(const AName: JSON_String; ACreate: Boolean): TROJSONObject;
|
|
function GetStringValueByName(const AName: JSON_String): JSON_String;
|
|
function GetNumberValueByName(const AName: JSON_String): Variant;
|
|
function GetBooleanValueByName(const AName: JSON_String): Boolean;
|
|
function GetVariantValueByName(const AName: JSON_String): Variant;
|
|
end;
|
|
|
|
TROJSONArray = class(TCollection)
|
|
private
|
|
protected
|
|
function GetItems(Index: Integer): TROJSONValue;
|
|
procedure SetItems(Index: Integer; const Value: TROJSONValue);
|
|
Procedure IntSaveToStream(AStream: TStream);
|
|
Procedure IntLoadFromStream(AStream: TStream);
|
|
public
|
|
constructor Create;
|
|
function Add: TROJSONValue;
|
|
Procedure SaveToStream(AStream: TStream; AUTF8Stream: boolean = True);
|
|
Procedure LoadFromStream(AStream: TStream; AUTF8Stream: boolean = True);
|
|
property Items[Index: Integer] :TROJSONValue read GetItems write SetItems; default;
|
|
function FindObjectByStringValue(const AName, aValue: JSON_String): TROJSONObject;
|
|
function AddObject: TROJSONValue;
|
|
function AddStringValue(const AValue: JSON_String):TROJSONValue;
|
|
function AddNumberValue(const AValue: Variant):TROJSONValue;
|
|
function AddBooleanValue(const AValue: Boolean):TROJSONValue;
|
|
function AddNullValue:TROJSONValue;
|
|
function AddArrayValue:TROJSONValue;
|
|
function AddVariantValue(const AValue: Variant):TROJSONValue;
|
|
end;
|
|
|
|
function JSON_ParseStream(AData: TStream; aUTF8Stream:Boolean = True):TROJSONValue;
|
|
|
|
implementation
|
|
|
|
uses Variants,uROBinaryHelpers, uROClasses;
|
|
|
|
const
|
|
JSON_streamID = 'stream';
|
|
|
|
{$IFDEF DELPHI7UP}
|
|
var
|
|
JSON_FormatSettings: TFormatSettings;
|
|
{$ENDIF}
|
|
|
|
function ReadNumber(AStream: TStream):Variant; forward;
|
|
function ReadString(AStream: TStream):JSON_String;forward;
|
|
function ReadChar(AStream: TStream;ASkipSpaces: Boolean=True):JSON_Char;forward;
|
|
function ReadNextChar(AStream: TStream;ASkipSpaces: Boolean=True):JSON_Char;forward;
|
|
|
|
procedure WriteChar(AStream: TStream;AChar :JSON_Char);forward;
|
|
procedure WriteString(AStream: TStream; AValue: JSON_String); forward;
|
|
procedure WriteIdent(AStream: TStream; AValue: JSON_String); forward;
|
|
procedure WriteNumber(AStream: TStream; AValue: Variant); forward;
|
|
|
|
function WriteVarianttoStreamObject(aValue: Variant):TROJSONObject;
|
|
var
|
|
lStream:TMemoryStream;
|
|
i: integer;
|
|
lArray: TROJSONArray;
|
|
begin
|
|
lStream:= BinaryFromVariant(aValue);
|
|
try
|
|
Result := TROJSONObject.Create;
|
|
lArray := Result.AddArrayProperty(JSON_streamID).ASArray;
|
|
For i := 0 to lStream.Size-1 do
|
|
lArray.AddNumberValue(PByteArray(lStream.Memory)^[i]);
|
|
finally
|
|
lStream.Free;
|
|
end;
|
|
end;
|
|
|
|
function ReadVariantFromStreamObject(AObject: TROJSONObject): Variant;
|
|
var
|
|
lProp: TROJSONProperty;
|
|
lArray: TROJSONArray;
|
|
i: integer;
|
|
lStream: TMemoryStream;
|
|
begin
|
|
Result:= Null;
|
|
lProp := AObject.FindItem(JSON_streamID);
|
|
if (lProp = nil) or (lProp.ValueType <> jdtArray) then Exit;
|
|
lArray := lProp.ASArray;
|
|
lStream:= TMemoryStream.Create;
|
|
try
|
|
lStream.Size := lArray.Count;
|
|
for i:=0 to lArray.Count-1 do
|
|
PByteArray(lStream.Memory)^[i]:= byte(lArray[i].GetAsNumber);
|
|
Result:= VariantFromBinary(lStream);
|
|
finally
|
|
lStream.Free;
|
|
end;
|
|
end;
|
|
|
|
Procedure JSON_Error_IncompatibleDataType;
|
|
begin
|
|
raise Exception.Create('Incompatible datatype');
|
|
end;
|
|
|
|
|
|
Procedure JSON_Error_PropertyNotFound(AName: JSON_String);
|
|
begin
|
|
raise Exception.Create(AName+' property not found.');
|
|
end;
|
|
|
|
Procedure JSON_Error(AStream: TStream);
|
|
begin
|
|
if AStream <> nil then
|
|
raise Exception.Create('This stream is incompatible with JSON format. Position:'+IntToStr(AStream.Position))
|
|
else
|
|
raise Exception.Create('This stream is incompatible with JSON format');
|
|
end;
|
|
|
|
procedure WriteChar(AStream: TStream;AChar :JSON_Char);
|
|
begin
|
|
AStream.WriteBuffer(AChar,SizeOf(JSON_Char));
|
|
end;
|
|
|
|
procedure WriteIdent(AStream: TStream; AValue: JSON_String);
|
|
begin
|
|
AStream.WriteBuffer(pointer(aValue)^,SizeOf(JSON_Char)*Length(AValue));
|
|
end;
|
|
|
|
procedure WriteNumber(AStream: TStream; AValue: Variant);
|
|
var
|
|
s: string;
|
|
c: char;
|
|
begin
|
|
case VarType(AValue) of
|
|
varShortInt,
|
|
varSmallint,
|
|
varByte,
|
|
varWord,
|
|
varInteger: s:= IntToStr(AValue);
|
|
varLongWord,
|
|
varInt64: s := IntToStr(aValue);
|
|
{$IFNDEF FPC}
|
|
varDate,
|
|
varSingle,
|
|
{$IFDEF DELPHI7UP}
|
|
varDouble: s:= FloatToStr(AValue,JSON_FormatSettings);
|
|
{$ELSE}
|
|
varDouble: s:= StringReplace(FloatToStr(AValue), '.', DecimalSeparator, []);
|
|
{$ENDIF}
|
|
|
|
{$IFDEF DELPHI7UP}
|
|
varCurrency: s:= CurrToStr(AValue,JSON_FormatSettings);
|
|
{$ELSE}
|
|
varCurrency: s:= StringReplace(CurrToStr(AValue), '.', DecimalSeparator, []);
|
|
{$ENDIF}
|
|
|
|
{$ENDIF}
|
|
else
|
|
c:= DecimalSeparator;
|
|
try
|
|
DecimalSeparator := '.';
|
|
s:= VarToStr(AValue);
|
|
finally
|
|
DecimalSeparator := c;
|
|
end;
|
|
end;
|
|
WriteIdent(AStream,s);
|
|
end;
|
|
|
|
procedure WriteString(AStream: TStream; AValue: JSON_String);
|
|
|
|
procedure WriteTag(const WC: JSON_Char);
|
|
begin
|
|
WriteChar(AStream,'\');
|
|
WriteChar(AStream,wc);
|
|
end;
|
|
|
|
var
|
|
i,j: integer;
|
|
s: string;
|
|
begin
|
|
WriteChar(AStream, '"');
|
|
for i := 1 to Length(AValue) do begin
|
|
case ord(AValue[i]) of
|
|
22 {"} : WriteTag('"');
|
|
92 {\} : WriteTag('\');
|
|
47 {/} : WriteTag('/');
|
|
08 : WriteTag('b');
|
|
09 : WriteTag('t');
|
|
10 : WriteTag('n');
|
|
12 : WriteTag('f');
|
|
13 : WriteTag('r');
|
|
32, 33,
|
|
35..46,
|
|
48..91,
|
|
93..127
|
|
{$IFNDEF WRITE_ASCII_COMPATIBLE},128..$FFFF{$ENDIF}: WriteChar(AStream,AValue[i]);
|
|
else
|
|
WriteTag('u');
|
|
s := IntToHex(ord(AValue[i]),4);
|
|
for j:=1 to Length(s) do
|
|
WriteChar(AStream,JSON_Char(ord(s[j])));
|
|
end;
|
|
end;
|
|
WriteChar(AStream, '"');
|
|
end;
|
|
|
|
function ReadNumber(AStream: TStream):Variant;
|
|
var
|
|
s: JSON_String;
|
|
s1: string;
|
|
lchar: JSON_Char;
|
|
begin
|
|
Result:=Null;
|
|
s:='';
|
|
lchar := ReadChar(AStream);
|
|
while true do begin
|
|
if ord(lchar) in [43, 45,46, 48..57, 69, 101 {+-.0123456789Ee}] then begin
|
|
s := s+lchar;
|
|
AStream.ReadBuffer(lchar, SizeOf(lchar));
|
|
end
|
|
else begin
|
|
AStream.Seek(-SizeOf(JSON_Char),soFromCurrent);
|
|
Break;
|
|
end;
|
|
end;
|
|
s1 := s;
|
|
if (pos('.', s1) >0) or (pos('e', s1) >0) or (pos('E', s1) >0) then
|
|
{$IFDEF DELPHI7UP}
|
|
Result := StrToFloat(s1,JSON_FormatSettings)
|
|
{$ELSE}
|
|
Result := StrToFloat(StringReplace(s1, '.', DecimalSeparator, []))
|
|
{$ENDIF}
|
|
|
|
else
|
|
Result := StrToInt64(s1);
|
|
end;
|
|
|
|
function ReadChar(AStream: TStream;ASkipSpaces: Boolean=True):JSON_Char;
|
|
var
|
|
f: boolean;
|
|
begin
|
|
f := True;
|
|
{$IFDEF FPC}
|
|
Result:=#0;
|
|
{$ENDIF}
|
|
While F do begin
|
|
AStream.ReadBuffer(Result, SizeOf(Result));
|
|
f := ASkipSpaces and (ord(Result) in [ord(' '), 10, 13, 9]);
|
|
end;
|
|
end;
|
|
|
|
function HexcodeToChar(AValue: JSON_String): JSON_Char;
|
|
var
|
|
i,j,k: integer;
|
|
begin
|
|
k:=0;
|
|
for i:= 1 to Length(aValue) do begin
|
|
j:= ord(AValue[i]);
|
|
case j of
|
|
48..57: dec(j,48);
|
|
65..70: dec(j, 55);
|
|
97..120: dec(j, 87);
|
|
else
|
|
raise Exception.Create('can''t decode hex string:'''+aValue+'''');
|
|
end;
|
|
k:= k*16 + j;
|
|
end;
|
|
Result:= JSon_Char(k);
|
|
end;
|
|
|
|
function ReadString(AStream: TStream):JSON_String;
|
|
var
|
|
LChar: JSON_Char;
|
|
s: JSON_String;
|
|
begin
|
|
Result:='';
|
|
lchar := ReadChar(AStream,False);
|
|
while lChar <> '"' do begin
|
|
if lchar = '\' then begin
|
|
lchar:=ReadChar(AStream,False);
|
|
if lchar = 'n' then Result := Result + #0010
|
|
else if lchar = 'r' then Result := Result + #0013
|
|
else if lchar = 't' then Result := Result + #0009
|
|
else if lchar = '"' then Result := Result + lchar
|
|
else if lchar = '\' then Result := Result + lchar
|
|
else if lchar = '/' then Result := Result + lchar
|
|
else if lchar = 'u' then begin
|
|
SetLength(s, 4);
|
|
AStream.ReadBuffer(Pointer(s)^,4*sizeOf(JSON_Char));
|
|
Result:=Result + HexCodetoChar(s);
|
|
end
|
|
else if lchar = 'b' then Result := Result + #0008
|
|
else if lchar = 'f' then Result := Result + #0012
|
|
else JSON_Error(AStream);
|
|
end
|
|
else
|
|
Result := Result + lChar;
|
|
lchar := ReadChar(AStream,False);
|
|
end;
|
|
end;
|
|
|
|
|
|
function ReadNextChar(AStream: TStream;ASkipSpaces: Boolean=True):JSON_Char;
|
|
var
|
|
p: int64;
|
|
begin
|
|
p := AStream.Position;
|
|
try
|
|
Result := ReadChar(AStream, ASkipSpaces);
|
|
finally
|
|
AStream.Position := p;
|
|
end;
|
|
end;
|
|
|
|
{ TROJSONValue }
|
|
|
|
|
|
procedure TROJSONValue.Clear;
|
|
begin
|
|
if FValueType <> jdtNull then begin
|
|
if FValueType in [jdtObject, jdtArray] then FreeAndNil(FObject);
|
|
FValueType := jdtNull;
|
|
FValue := Null;
|
|
end;
|
|
end;
|
|
|
|
|
|
constructor TROJSONValue.Create(Collection: TCollection);
|
|
begin
|
|
inherited;
|
|
FObject := nil;
|
|
FValueType := jdtNull;
|
|
FValue := Null;
|
|
end;
|
|
|
|
destructor TROJSONValue.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited;
|
|
end;
|
|
|
|
function TROJSONValue.GetAsArray: TROJSONArray;
|
|
begin
|
|
if (FValueType = jdtArray) then
|
|
Result:= TROJSONArray(FObject)
|
|
else begin
|
|
{$IFDEF RaiseInvalidDataTypeException}
|
|
JSON_Error_IncompatibleDataType;
|
|
{$ENDIF}
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
function TROJSONValue.GetAsBoolean: Boolean;
|
|
begin
|
|
if FValueType = jdtBoolean then
|
|
Result := FValue
|
|
else begin
|
|
{$IFDEF RaiseInvalidDataTypeException}
|
|
JSON_Error_IncompatibleDataType;
|
|
{$ENDIF}
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TROJSONValue.GetAsNumber: Variant;
|
|
begin
|
|
if FValueType = jdtNumber then
|
|
Result := FValue
|
|
else begin
|
|
{$IFDEF RaiseInvalidDataTypeException}
|
|
JSON_Error_IncompatibleDataType;
|
|
{$ENDIF}
|
|
Result := Null;
|
|
end;
|
|
end;
|
|
|
|
function TROJSONValue.GetAsObject: TROJSONObject;
|
|
begin
|
|
if (FValueType = jdtObject) then
|
|
Result:= TROJSONObject(FObject)
|
|
else begin
|
|
{$IFDEF RaiseInvalidDataTypeException}
|
|
JSON_Error_IncompatibleDataType;
|
|
{$ENDIF}
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
function TROJSONValue.GetAsString: JSON_String;
|
|
begin
|
|
Result := FValue;
|
|
end;
|
|
|
|
function TROJSONValue.GetVarValue: Variant;
|
|
begin
|
|
case FValueType of
|
|
jdtNull : Result:= Null;
|
|
jdtString: Result:= AsString;
|
|
jdtNumber: Result:= AsNumber;
|
|
jdtBoolean: Result := AsBoolean;
|
|
jdtArray: ;
|
|
jdtObject: Result := ReadVariantFromStreamObject(AsObject);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONValue.IntLoadFromStream(AStream: TStream);
|
|
var
|
|
lChar: JSON_Char;
|
|
s: JSON_String;
|
|
begin
|
|
lChar := ReadNextChar(AStream);
|
|
if lChar = '"' then begin //string
|
|
ReadChar(AStream);
|
|
AsString:=ReadString(AStream);
|
|
end
|
|
else if lChar = '{' then begin //object
|
|
AsObject := TROJSONObject.Create;
|
|
AsObject.IntLoadFromStream(AStream);
|
|
end
|
|
else if lChar = '[' then begin // array
|
|
AsArray := TROJSONArray.Create;
|
|
AsArray.IntLoadFromStream(AStream);
|
|
end
|
|
else if lchar = 't' then begin //true
|
|
ReadChar(AStream);
|
|
SetLength(s, 3);
|
|
AStream.ReadBuffer(pointer(s)^, 3*sizeof(JSON_Char));
|
|
if s <> 'rue' then JSON_Error(AStream);
|
|
AsBoolean:=True;
|
|
end
|
|
else if lchar = 'f' then begin //false
|
|
ReadChar(AStream);
|
|
SetLength(s, 4);
|
|
AStream.ReadBuffer(pointer(s)^, 4*sizeof(JSON_Char));
|
|
if s <> 'alse' then JSON_Error(AStream);
|
|
AsBoolean:=False;
|
|
end
|
|
else if lchar = 'n' then begin //null
|
|
ReadChar(AStream);
|
|
SetLength(s, 3);
|
|
AStream.ReadBuffer(pointer(s)^, 3*sizeof(JSON_Char));
|
|
if s <> 'ull' then JSON_Error(AStream);
|
|
Clear;
|
|
end
|
|
else begin // number
|
|
AsNumber:= ReadNumber(AStream);
|
|
end
|
|
end;
|
|
|
|
procedure TROJSONValue.IntSaveToStream(AStream: TStream);
|
|
begin
|
|
case FValueType of
|
|
jdtObject: AsObject.IntSaveToStream(AStream);
|
|
jdtArray: ASArray.IntSaveToStream(AStream);
|
|
jdtNull: WriteIdent(AStream, 'null');
|
|
jdtBoolean: if GetAsBoolean then
|
|
WriteIdent(AStream, 'true')
|
|
else
|
|
WriteIdent(AStream, 'false');
|
|
jdtString: WriteString(AStream, AsString);
|
|
jdtNumber: WriteNumber(AStream, AsNumber);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONValue.LoadFromStream(AStream: TStream; AUTF8Stream: boolean);
|
|
var
|
|
lStream: TMemoryStream;
|
|
s: UTF8String;
|
|
w: JSON_String;
|
|
lPos: int64;
|
|
begin
|
|
lpos := AStream.Position;
|
|
try
|
|
if AUTF8Stream then begin
|
|
lStream:= TMemoryStream.Create;
|
|
try
|
|
SetLength(s, AStream.Size - AStream.Position);
|
|
AStream.Read(pointer(s)^, Length(s));
|
|
if Copy(s,1,3) = UTF8String(#$EF#$BB#$BF) then delete(s,1,3);
|
|
|
|
w:= UTF8ToString(s);
|
|
s:='';
|
|
lStream.Size := Length(w)*SizeOf(JSON_Char);
|
|
Move(pointer(w)^,lStream.Memory^, lStream.Size);
|
|
w:='';
|
|
IntLoadFromStream(lStream);
|
|
finally
|
|
lStream.Free;
|
|
end;
|
|
end
|
|
else begin
|
|
IntLoadFromStream(AStream);
|
|
end;
|
|
except
|
|
AStream.Position:= lPos;
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONValue.SaveToStream(AStream: TStream; AUTF8Stream: boolean);
|
|
var
|
|
lStream: TMemoryStream;
|
|
s: UTF8String;
|
|
w: JSON_String;
|
|
begin
|
|
if AUTF8Stream then begin
|
|
lStream:= TMemoryStream.Create;
|
|
try
|
|
IntSaveToStream(lStream);
|
|
SetString(w, JSON_PChar(lStream.Memory), lStream.Size div SizeOf(JSON_Char));
|
|
finally
|
|
lStream.Free;
|
|
end;
|
|
s:=UTF8Encode(W);
|
|
w:='';
|
|
AStream.Write(pointer(s)^, Length(s));
|
|
end
|
|
else begin
|
|
IntSaveToStream(AStream);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONValue.SetAsArray(const Value: TROJSONArray);
|
|
begin
|
|
if (Value <> FObject) or (FValueType <> jdtArray) then begin
|
|
Clear;
|
|
if Value <> nil then begin
|
|
FObject := Value;
|
|
FValueType := jdtArray;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONValue.SetAsBoolean(const Value: Boolean);
|
|
begin
|
|
if FValueType <> jdtBoolean then begin
|
|
Clear;
|
|
FValueType := jdtBoolean;
|
|
end;
|
|
FValue := Value;
|
|
end;
|
|
|
|
procedure TROJSONValue.SetAsNull;
|
|
begin
|
|
Clear;
|
|
end;
|
|
|
|
procedure TROJSONValue.SetAsNumber(const Value: Variant);
|
|
begin
|
|
if FValueType <> jdtNumber then begin
|
|
Clear;
|
|
FValueType := jdtNumber;
|
|
end;
|
|
FValue:= Value;
|
|
end;
|
|
|
|
procedure TROJSONValue.SetAsObject(const Value: TROJSONObject);
|
|
begin
|
|
if (Value <> FObject) or (FValueType <> jdtObject) then begin
|
|
Clear;
|
|
if Value <> nil then begin
|
|
FObject := Value;
|
|
FValueType := jdtObject;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONValue.SetVarValue(const Value: Variant);
|
|
begin
|
|
if VarIsNull(Value) or VarIsEmpty(Value) then SetAsNull
|
|
else if TVarData(Value).VType = varBoolean then AsBoolean := Value
|
|
else if VarIsStr(Value) then AsString := Value
|
|
else if VarIsNumeric(Value) or (TVarData(Value).VType = varDate) then AsNumber := Value
|
|
else AsObject := WriteVarianttoStreamObject(Value);
|
|
end;
|
|
|
|
constructor TROJSONValue.Create(aType: TROJSONDataType);
|
|
begin
|
|
inherited Create(nil);
|
|
FObject := nil;
|
|
case aType of
|
|
jdtObject: FObject := TROJSONObject.Create;
|
|
jdtArray: FObject := TROJSONArray.Create;
|
|
end;
|
|
FValueType := aType;
|
|
FValue := Null;
|
|
end;
|
|
|
|
{ TROJSONProperty }
|
|
|
|
|
|
procedure TROJSONValue.SetAsString(const Value: JSON_String);
|
|
begin
|
|
if FValueType <> jdtString then begin
|
|
Clear;
|
|
FValueType := jdtString;
|
|
end;
|
|
FValue:= Value;
|
|
end;
|
|
|
|
{ TROJSONProperty }
|
|
|
|
procedure TROJSONProperty.IntLoadFromStream(AStream: TStream);
|
|
begin
|
|
// inherited;
|
|
if ReadChar(AStream) <> '"' then JSON_Error(AStream);
|
|
FName := ReadString(Astream);
|
|
if ReadChar(AStream) <> ':' then JSON_Error(AStream);
|
|
inherited IntLoadFromStream(AStream);
|
|
end;
|
|
|
|
procedure TROJSONProperty.IntSaveToStream(AStream: TStream);
|
|
begin
|
|
WriteString(AStream,FName);
|
|
WriteChar(AStream,':');
|
|
inherited IntSaveToStream(Astream);
|
|
end;
|
|
|
|
procedure TROJSONProperty.SetName(const Value: JSON_String);
|
|
begin
|
|
FName := Value;
|
|
end;
|
|
|
|
{ TROJSONObject }
|
|
|
|
function TROJSONObject.Add: TROJSONProperty;
|
|
begin
|
|
Result:=TROJSONProperty(inherited Add);
|
|
end;
|
|
|
|
function TROJSONObject.AddArrayProperty(
|
|
const AName: JSON_String): TROJSONProperty;
|
|
begin
|
|
Result:=Add;
|
|
Result.Name := AName;
|
|
Result.ASArray := TROJSONArray.Create;
|
|
end;
|
|
|
|
function TROJSONObject.AddBooleanProperty(const AName: JSON_String;
|
|
const AValue: Boolean): TROJSONProperty;
|
|
begin
|
|
Result:=Add;
|
|
Result.Name := AName;
|
|
Result.AsBoolean := AValue;
|
|
end;
|
|
|
|
function TROJSONObject.AddNullProperty(const AName: JSON_String): TROJSONProperty;
|
|
begin
|
|
Result:=Add;
|
|
Result.Name := AName;
|
|
Result.SetAsNull;
|
|
end;
|
|
|
|
function TROJSONObject.AddNumberProperty(const AName: JSON_String;
|
|
const AValue: Variant): TROJSONProperty;
|
|
begin
|
|
Result:=Add;
|
|
Result.Name := AName;
|
|
Result.AsNumber := AValue;
|
|
end;
|
|
|
|
function TROJSONObject.AddObjectProperty(
|
|
const AName: JSON_String): TROJSONProperty;
|
|
begin
|
|
Result:=Add;
|
|
Result.Name := AName;
|
|
Result.AsObject := TROJSONObject.Create;
|
|
end;
|
|
|
|
function TROJSONObject.AddStringProperty(const AName,
|
|
aValue: JSON_String): TROJSONProperty;
|
|
begin
|
|
Result:=Add;
|
|
Result.Name := AName;
|
|
Result.AsString := aValue;
|
|
end;
|
|
|
|
constructor TROJSONObject.Create;
|
|
begin
|
|
inherited Create(TROJSONProperty);
|
|
end;
|
|
|
|
function TROJSONObject.GetArrayItemByName(const AName: JSON_String;
|
|
ACreate: Boolean): TROJSONArray;
|
|
var
|
|
aItem : TROJSONProperty;
|
|
begin
|
|
Result := nil;
|
|
aItem := FindItem(AName);
|
|
if (AItem = nil) and ACreate then
|
|
aItem := AddArrayProperty(Aname);
|
|
|
|
if aItem <> nil then begin
|
|
if aItem.ValueType = jdtArray then
|
|
result := aItem.ASArray
|
|
else
|
|
JSON_Error_IncompatibleDataType;
|
|
end;
|
|
end;
|
|
|
|
function TROJSONObject.FindItem(const AName: JSON_String): TROJSONProperty;
|
|
var
|
|
i: integer;
|
|
begin
|
|
i := IndexOf(AName);
|
|
if i = -1 then
|
|
Result := nil
|
|
else
|
|
Result := Items[i];
|
|
end;
|
|
|
|
function TROJSONObject.IndexOf(const AName: JSON_String): integer;
|
|
begin
|
|
For Result:= 0 to Count - 1 do
|
|
if Items[Result].Name = AName then Exit;
|
|
Result := -1;
|
|
end;
|
|
|
|
function TROJSONObject.GetItems(Index: Integer): TROJSONProperty;
|
|
begin
|
|
Result:= TROJSONProperty(GetItem(Index));
|
|
end;
|
|
|
|
function TROJSONObject.GetObjectItemByName(const AName: JSON_String;
|
|
ACreate: Boolean): TROJSONObject;
|
|
var
|
|
aItem : TROJSONProperty;
|
|
begin
|
|
Result := nil;
|
|
aItem := FindItem(AName);
|
|
if (AItem = nil) and ACreate then begin
|
|
aItem := AddObjectProperty(AName);
|
|
end;
|
|
if aItem <> nil then begin
|
|
if aItem.ValueType = jdtObject then
|
|
result := aItem.AsObject
|
|
else
|
|
JSON_Error_IncompatibleDataType;
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONObject.IntLoadFromStream(AStream: TStream);
|
|
var
|
|
AChar: JSON_Char;
|
|
begin
|
|
if ReadChar(AStream ) = '{' then begin
|
|
AChar := ReadChar(AStream);
|
|
while AChar <> '}' do begin
|
|
if AChar = ',' then begin
|
|
AChar:= ReadChar(AStream);
|
|
continue;
|
|
end
|
|
else if AChar = '"' then begin
|
|
AStream.Seek( -SizeOf(JSON_Char), soFromCurrent);
|
|
TROJSONProperty(Add).IntLoadFromStream(AStream);
|
|
end
|
|
else
|
|
// only " and , supported here
|
|
JSON_Error(AStream);
|
|
AChar:= ReadChar(AStream);
|
|
end;
|
|
end
|
|
else begin
|
|
JSON_Error(AStream);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONObject.IntSaveToStream(AStream: TStream);
|
|
var
|
|
i: integer;
|
|
begin
|
|
WriteChar(AStream, '{');
|
|
For i := 0 to Count -1 do begin
|
|
Items[i].IntSaveToStream(AStream);
|
|
if i <> Count -1 then WriteChar(AStream, ',');
|
|
end;
|
|
WriteChar(AStream, '}');
|
|
end;
|
|
|
|
procedure TROJSONObject.SaveToStream(AStream: TStream; AUTF8Stream: boolean);
|
|
var
|
|
temp: TROJSONValue;
|
|
begin
|
|
if AUTF8Stream then begin
|
|
temp:= TROJSONValue.Create(nil);
|
|
try
|
|
temp.AsObject := Self;
|
|
temp.SaveToStream(AStream,AUTF8Stream);
|
|
finally
|
|
temp.FValueType:= jdtNull; // for preventing freeing object
|
|
temp.Free;
|
|
end;
|
|
end
|
|
else begin
|
|
IntSaveToStream(AStream);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONObject.SetItems(Index: Integer;
|
|
const Value: TROJSONProperty);
|
|
begin
|
|
SetItem(Index, Value);
|
|
end;
|
|
|
|
function TROJSONObject.GetStringValueByName(
|
|
const AName: JSON_String): JSON_String;
|
|
var
|
|
aItem : TROJSONProperty;
|
|
begin
|
|
Result := '';
|
|
aItem := ItemByName(AName);
|
|
if aItem <> nil then begin
|
|
if aItem.ValueType = jdtString then
|
|
result := aItem.AsString
|
|
else
|
|
JSON_Error_IncompatibleDataType;
|
|
end;
|
|
end;
|
|
|
|
function TROJSONObject.GetBooleanValueByName(
|
|
const AName: JSON_String): Boolean;
|
|
var
|
|
aItem : TROJSONProperty;
|
|
begin
|
|
Result := False;
|
|
aItem := ItemByName(AName);
|
|
if aItem <> nil then begin
|
|
if aItem.ValueType = jdtBoolean then
|
|
result := aItem.AsBoolean
|
|
else
|
|
JSON_Error_IncompatibleDataType;
|
|
end;
|
|
end;
|
|
|
|
function TROJSONObject.GetNumberValueByName(
|
|
const AName: JSON_String): Variant;
|
|
var
|
|
aItem : TROJSONProperty;
|
|
begin
|
|
Result := Null;
|
|
aItem := ItemByName(AName);
|
|
if aItem <> nil then begin
|
|
if aItem.ValueType = jdtNumber then
|
|
result := aItem.AsNumber
|
|
else
|
|
JSON_Error_IncompatibleDataType;
|
|
end;
|
|
end;
|
|
|
|
function TROJSONObject.AddVariantProperty(const AName: JSON_String;
|
|
const AValue: Variant): TROJSONProperty;
|
|
begin
|
|
Result:=Add;
|
|
Result.Name := AName;
|
|
Result.VarValue := AValue;
|
|
end;
|
|
|
|
function TROJSONObject.GetVariantValueByName(
|
|
const AName: JSON_String): Variant;
|
|
var
|
|
aItem : TROJSONProperty;
|
|
begin
|
|
Result := '';
|
|
aItem := ItemByName(AName);
|
|
if aItem <> nil then Result:= aItem.VarValue;
|
|
end;
|
|
|
|
function TROJSONObject.ItemByName(
|
|
const AName: JSON_String): TROJSONProperty;
|
|
begin
|
|
Result := FindItem(AName);
|
|
if (Result = nil) then JSON_Error_PropertyNotFound(AName);
|
|
end;
|
|
|
|
procedure TROJSONObject.LoadFromStream(AStream: TStream; AUTF8Stream: boolean);
|
|
var
|
|
temp: TROJSONValue;
|
|
begin
|
|
if AUTF8Stream then begin
|
|
temp:= TROJSONValue.Create(nil);
|
|
try
|
|
temp.AsObject := Self;
|
|
temp.LoadFromStream(AStream,AUTF8Stream);
|
|
finally
|
|
temp.FValueType:= jdtNull; // for preventing freeing object
|
|
temp.Free;
|
|
end;
|
|
end
|
|
else begin
|
|
IntLoadFromStream(AStream);
|
|
end;
|
|
end;
|
|
|
|
{ TROJSONArray }
|
|
|
|
function TROJSONArray.Add: TROJSONValue;
|
|
begin
|
|
Result:= TROJSONValue(inherited Add);
|
|
end;
|
|
|
|
function TROJSONArray.AddArrayValue: TROJSONValue;
|
|
begin
|
|
Result:=Add;
|
|
Result.ASArray:=TROJSONArray.Create;
|
|
end;
|
|
|
|
function TROJSONArray.AddBooleanValue(const AValue: Boolean): TROJSONValue;
|
|
begin
|
|
Result:=Add;
|
|
Result.AsBoolean:= AValue;
|
|
end;
|
|
|
|
function TROJSONArray.AddNullValue: TROJSONValue;
|
|
begin
|
|
Result:=Add;
|
|
Result.SetAsNull;
|
|
end;
|
|
|
|
function TROJSONArray.AddNumberValue(const AValue: Variant): TROJSONValue;
|
|
begin
|
|
Result:=Add;
|
|
Result.AsNumber:= AValue;
|
|
end;
|
|
|
|
function TROJSONArray.AddObject: TROJSONValue;
|
|
begin
|
|
Result:= Add;
|
|
Result.AsObject := TROJSONObject.Create;
|
|
end;
|
|
|
|
function TROJSONArray.AddStringValue(
|
|
const AValue: JSON_String): TROJSONValue;
|
|
begin
|
|
Result:=Add;
|
|
Result.AsString:= AValue;
|
|
end;
|
|
|
|
function TROJSONArray.AddVariantValue(const AValue: Variant): TROJSONValue;
|
|
begin
|
|
Result:=Add;
|
|
Result.VarValue := AValue;
|
|
end;
|
|
|
|
constructor TROJSONArray.Create;
|
|
begin
|
|
inherited Create(TROJSONValue);
|
|
end;
|
|
|
|
function TROJSONArray.FindObjectByStringValue(const AName,
|
|
aValue: JSON_String): TROJSONObject;
|
|
var
|
|
i: integer;
|
|
lProp: TROJSONProperty;
|
|
begin
|
|
for i := 0 to Count-1 do begin
|
|
if (Items[i].ValueType = jdtObject) then begin
|
|
Result := Items[i].AsObject;
|
|
lProp := Result.FindItem(AName);
|
|
if (lProp <> nil) and (lProp.ValueType = jdtString) and (lProp.AsString = aValue) then Exit;
|
|
end;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
function TROJSONArray.GetItems(Index: Integer): TROJSONValue;
|
|
begin
|
|
Result:= TROJSONValue(GetItem(Index));
|
|
end;
|
|
|
|
procedure TROJSONArray.IntLoadFromStream(AStream: TStream);
|
|
var
|
|
AChar: JSON_Char;
|
|
begin
|
|
if ReadChar(AStream ) = '[' then begin
|
|
AChar := ReadChar(AStream);
|
|
while AChar <> ']' do begin
|
|
if AChar <> ',' then AStream.Seek( -SizeOf(JSON_Char), soFromCurrent);
|
|
TROJSONValue(Add).IntLoadFromStream(AStream);
|
|
AChar := ReadChar(AStream);
|
|
end;
|
|
end
|
|
else begin
|
|
JSON_Error(AStream);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONArray.IntSaveToStream(AStream: TStream);
|
|
var
|
|
i: integer;
|
|
begin
|
|
WriteChar(AStream, '[');
|
|
For i := 0 to Count -1 do begin
|
|
Items[i].IntSaveToStream(AStream);
|
|
if i <> Count -1 then WriteChar(AStream, ',');
|
|
end;
|
|
WriteChar(AStream, ']');
|
|
end;
|
|
|
|
procedure TROJSONArray.LoadFromStream(AStream: TStream; AUTF8Stream: boolean);
|
|
var
|
|
temp: TROJSONValue;
|
|
begin
|
|
if AUTF8Stream then begin
|
|
temp:= TROJSONValue.Create(nil);
|
|
try
|
|
temp.ASArray := Self;
|
|
temp.LoadFromStream(AStream,AUTF8Stream);
|
|
finally
|
|
temp.FValueType:= jdtNull; // for preventing freeing object
|
|
temp.Free;
|
|
end;
|
|
end
|
|
else begin
|
|
IntLoadFromStream(AStream);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONArray.SaveToStream(AStream: TStream; AUTF8Stream: boolean);
|
|
var
|
|
temp: TROJSONValue;
|
|
begin
|
|
if AUTF8Stream then begin
|
|
temp:= TROJSONValue.Create(nil);
|
|
try
|
|
temp.ASArray := Self;
|
|
temp.SaveToStream(AStream,AUTF8Stream);
|
|
finally
|
|
temp.FValueType:= jdtNull; // for preventing freeing object
|
|
temp.Free;
|
|
end;
|
|
end
|
|
else begin
|
|
IntSaveToStream(AStream);
|
|
end;
|
|
end;
|
|
|
|
procedure TROJSONArray.SetItems(Index: Integer; const Value: TROJSONValue);
|
|
begin
|
|
SetItem(Index, Value);
|
|
end;
|
|
|
|
function JSON_ParseStream(AData: TStream;aUTF8Stream:Boolean):TROJSONValue;
|
|
var
|
|
lpos : int64;
|
|
begin
|
|
lpos := AData.Position;
|
|
try
|
|
Result := TROJSONValue.Create(nil);
|
|
Result.LoadFromStream(AData,aUTF8Stream);
|
|
except
|
|
AData.Position := lPos;
|
|
Raise;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF DELPHI7UP}
|
|
initialization
|
|
JSON_FormatSettings.DecimalSeparator := '.';
|
|
{$ENDIF}
|
|
end.
|
|
|