diff --git a/packages/fcl-json/src/fpjson.pp b/packages/fcl-json/src/fpjson.pp index fea27e4bc3..7c5f7accd8 100644 --- a/packages/fcl-json/src/fpjson.pp +++ b/packages/fcl-json/src/fpjson.pp @@ -36,14 +36,17 @@ type TFormatOption = (foSingleLineArray, // Array without CR/LF : all on one line foSingleLineObject, // Object without CR/LF : all on one line foDoNotQuoteMembers, // Do not quote object member names. - foUseTabchar); // Use tab characters instead of spaces. + foUseTabchar, // Use tab characters instead of spaces. + foSkipWhiteSpace); // Do not use whitespace at all TFormatOptions = set of TFormatOption; Const DefaultIndentSize = 2; DefaultFormat = []; AsJSONFormat = [foSingleLineArray,foSingleLineObject]; // These options make FormatJSON behave as AsJSON - + AsCompressedJSON = [foSingleLineArray,foSingleLineObject,foskipWhiteSpace]; // These options make FormatJSON behave as AsJSON with TJSONData.CompressedJSON=True + AsCompactJSON = [foSingleLineArray,foSingleLineObject,foskipWhiteSpace,foDoNotQuoteMembers]; // These options make FormatJSON behave as AsJSON with TJSONData.CompressedJSON=True and TJSONObject.UnquotedMemberNames=True + Type TJSONData = Class; @@ -68,6 +71,8 @@ Type TJSONData = class(TObject) private + Const + ElementSeps : Array[Boolean] of TJSONStringType = (', ',','); Class Var FCompressedJSON : Boolean; Class Var FElementSep : TJSONStringType; class procedure DetermineElementSeparators; @@ -103,6 +108,7 @@ Type public Constructor Create; virtual; Procedure Clear; virtual; Abstract; + Procedure DumpJSON(S : TStream); // Get enumerator function GetEnumerator: TBaseJSONEnumerator; virtual; Function FindPath(Const APath : TJSONStringType) : TJSONdata; @@ -442,7 +448,13 @@ Type TJSONObject = class(TJSONData) private - Class var FUnquotedElementNames: Boolean; + Const + ElementStart : Array[Boolean] of TJSONStringType = ('"',''); + SpacedQuoted : Array[Boolean] of TJSONStringType = ('" : ',' : '); + UnSpacedQuoted : Array[Boolean] of TJSONStringType = ('":',':'); + ObjStartSeps : Array[Boolean] of TJSONStringType = ('{ ','{'); + ObjEndSeps : Array[Boolean] of TJSONStringType = (' }','}'); + Class var FUnquotedMemberNames: Boolean; Class var FObjStartSep,FObjEndSep,FElementEnd,FElementStart : TJSONStringType; Class procedure DetermineElementQuotes; Private @@ -469,8 +481,8 @@ Type procedure SetObjects(const AName : String; const AValue: TJSONObject); procedure SetQWords(AName : String; AValue: QWord); procedure SetStrings(const AName : String; const AValue: TJSONStringType); - class function GetUnquotedElementNames: Boolean; static; - class procedure SetUnquotedElementNames(AValue: Boolean); static; + class function GetUnquotedMemberNames: Boolean; static; + class procedure SetUnquotedMemberNames(AValue: Boolean); static; protected Function DoFindPath(Const APath : TJSONStringType; Out NotFound : TJSONStringType) : TJSONdata; override; Procedure Converterror(From : Boolean); @@ -498,7 +510,7 @@ Type Constructor Create(const Elements : Array of Const); overload; destructor Destroy; override; class function JSONType: TJSONType; override; - Class Property UnquotedElementNames : Boolean Read GetUnquotedElementNames Write SetUnquotedElementNames; + Class Property UnquotedMemberNames : Boolean Read GetUnquotedMemberNames Write SetUnquotedMemberNames; Function Clone : TJSONData; override; function GetEnumerator: TBaseJSONEnumerator; override; // Examine @@ -1011,6 +1023,52 @@ begin Clear; end; +procedure TJSONData.DumpJSON(S: TStream); + + Procedure W(T : String); + + begin + if (T<>'') then + S.WriteBuffer(T[1],Length(T)*SizeOf(Char)); + end; + +Var + I,C : Integer; + O : TJSONObject; + +begin + Case JSONType of + jtObject : + begin + O:=TJSONObject(Self); + W('{'); + For I:=0 to O.Count-1 do + begin + if (I>0) then + W(','); + W('"'); + W(StringToJSONString(O.Names[i])); + W('":'); + O.Items[I].DumpJSON(S); + end; + W('}'); + end; + jtArray : + begin + W('['); + For I:=0 to Count-1 do + begin + if (I>0) then + W(','); + Items[I].DumpJSON(S); + end; + W(']'); + end + else + W(AsJSON) + end; +end; + class function TJSONData.GetCompressedJSON: Boolean; static; begin Result:=FCompressedJSON; @@ -1018,8 +1076,6 @@ end; class procedure TJSONData.DetermineElementSeparators; -Const - ElementSeps : Array[Boolean] of TJSONStringType = (', ',','); begin FElementSep:=ElementSeps[FCompressedJSON]; @@ -1963,25 +2019,31 @@ function TJSONArray.DoFormatJSON(Options: TFormatOptions; CurrentIndent, Var I : Integer; + MultiLine : Boolean; + SkipWhiteSpace : Boolean; + Ind : String; begin Result:='['; - if not (foSingleLineArray in Options) then + MultiLine:=Not (foSingleLineArray in Options); + SkipWhiteSpace:=foSkipWhiteSpace in Options; + Ind:=IndentString(Options, CurrentIndent+Indent); + if MultiLine then Result:=Result+sLineBreak; For I:=0 to Count-1 do begin - if not (foSingleLineArray in Options) then - Result:=Result+IndentString(Options, CurrentIndent+Indent); + if MultiLine then + Result:=Result+Ind; Result:=Result+Items[i].DoFormatJSON(Options,CurrentIndent+Indent,Indent); If (I'') then - begin - If (foSingleLineObject in Options) then - Result:=Result+', ' - else - Result:=Result+','+SLineBreak; - end; - If not (foSingleLineObject in Options) then - Result:=Result+IndentString(Options,CurrentIndent); + If (I>0) then + Result:=Result+Sep + else If MultiLine then + Result:=Result+Ind; S:=StringToJSONString(Names[i]); - If not (foDoNotQuoteMembers in options) then + If UseQuotes then S:='"'+S+'"'; - Result:=Result+S+' : '+Items[I].DoFormatJSON(Options,CurrentIndent,Indent); + Result:=Result+S+NSep+Items[I].DoFormatJSON(Options,CurrentIndent,Indent); end; If (Result<>'') then begin - if (foSingleLineObject in Options) then - Result:='{ '+Result+' }' - else + if MultiLine then Result:='{'+sLineBreak+Result+sLineBreak+indentString(options,CurrentIndent-Indent)+'}' + else + Result:=ObjStartSeps[SkipWhiteSpace]+Result+ObjEndSeps[SkipWhiteSpace] end else Result:='{}'; diff --git a/packages/fcl-json/tests/testjsondata.pp b/packages/fcl-json/tests/testjsondata.pp index 923361f0aa..0d9dbed354 100644 --- a/packages/fcl-json/tests/testjsondata.pp +++ b/packages/fcl-json/tests/testjsondata.pp @@ -1054,7 +1054,7 @@ begin inherited SetUp; SetDefaultInstanceTypes; TJSONData.CompressedJSON:=False; - TJSONObject.UnquotedElementNames:=False; + TJSONObject.UnquotedMemberNames:=False; end; Procedure TTestJSON.TestItemCount(J: TJSONData; Expected: Integer); @@ -2994,10 +2994,10 @@ begin TestJSONType(J[2],jtNumber); TestJSON(J,'[0, 1, 2]'); AssertEquals('FormatJSON, single line',J.AsJSON,J.FormatJSON([foSingleLineArray],1)); - AssertEquals('FormatJSON, single line','['+sLinebreak+' 0,'+sLinebreak+' 1,'+sLinebreak+' 2'+sLinebreak+']',J.FormatJSON()); - AssertEquals('FormatJSON, single line','['+sLinebreak+#9'0,'+sLinebreak+#9'1,'+sLinebreak+#9'2'+sLinebreak+']',J.FormatJSON([foUseTabChar],1)); + AssertEquals('FormatJSON, default','['+sLinebreak+' 0,'+sLinebreak+' 1,'+sLinebreak+' 2'+sLinebreak+']',J.FormatJSON()); + AssertEquals('FormatJSON, use tab','['+sLinebreak+#9'0,'+sLinebreak+#9'1,'+sLinebreak+#9'2'+sLinebreak+']',J.FormatJSON([foUseTabChar],1)); J.Add(TJSONObject.Create(['x',1,'y',2])); - AssertEquals('FormatJSON, single line','['+sLinebreak+#9'0,'+sLinebreak+#9'1,'+sLinebreak+#9'2,'+sLinebreak+#9'{'+sLineBreak+#9#9'"x" : 1,'+sLineBreak+#9#9'"y" : 2'+sLinebreak+#9'}'+sLineBreak+']',J.FormatJSON([foUseTabChar],1)); + AssertEquals('FormatJSON, use tab indentsize 1','['+sLinebreak+#9'0,'+sLinebreak+#9'1,'+sLinebreak+#9'2,'+sLinebreak+#9'{'+sLineBreak+#9#9'"x" : 1,'+sLineBreak+#9#9'"y" : 2'+sLinebreak+#9'}'+sLineBreak+']',J.FormatJSON([foUseTabChar],1)); finally J.Free end; @@ -3440,6 +3440,8 @@ begin try TestJSON(O,'{ "x" : 1, "y" : 2 }'); AssertEquals('Format equals JSON',O.AsJSON,O.FormatJSON([foSingleLineObject])); + AssertEquals('Format using SkipWhiteSpace','{"x":1,"y":2}',O.FormatJSON([foSingleLineObject,foSkipWhiteSpace])); + AssertEquals('Format using SkipWhiteSpace,unquotednames','{x:1,y:2}',O.FormatJSON([foSingleLineObject,foSkipWhiteSpace,foDoNotQuoteMembers])); AssertEquals('Format 1','{'+sLineBreak+' "x" : 1,'+sLineBreak+' "y" : 2'+sLineBreak+'}',O.FormatJSON([])); AssertEquals('Format 1','{'+sLineBreak+' x : 1,'+sLineBreak+' y : 2'+sLineBreak+'}',O.FormatJSON([foDoNotQuoteMembers])); AssertEquals('Format 1','{'+sLineBreak+#9'x : 1,'+sLineBreak+#9'y : 2'+sLineBreak+'}',O.FormatJSON([foUseTabChar,foDoNotQuoteMembers],1)); @@ -3517,7 +3519,7 @@ Var J : TJSONObject; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnquotedMemberNames:=True; J:=TJSONObject.Create([A,S]); try TestJSONType(J,jtObject); @@ -3562,7 +3564,7 @@ Var J : TJSONObject; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,Pchar(S)]); try TestJSONType(J,jtObject); @@ -3639,7 +3641,7 @@ Var begin TJSONData.CompressedJSON:=True; - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,S,B,T]); try TestJSONType(J,jtObject); @@ -3683,7 +3685,7 @@ Var J : TJSONObject; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,S]); try TestJSONType(J,jtObject); @@ -3728,7 +3730,7 @@ Var r : String; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,S]); try TestJSONType(J,jtObject); @@ -3771,7 +3773,7 @@ Var J : TJSONObject; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,S]); try TestJSONType(J,jtObject); @@ -3813,7 +3815,7 @@ Var J : TJSONObject; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,S]); try TestJSONType(J,jtObject); @@ -3852,7 +3854,7 @@ Var J : TJSONObject; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,TJSONObject.Create]); try TestItemCount(J,1); @@ -3893,7 +3895,7 @@ Var J : TJSONObject; begin - TJSONObject.UnquotedElementNames:=True; + TJSONObject.UnQuotedMemberNames:=True; J:=TJSONObject.Create([A,TJSONString.Create(S)]); try TestItemCount(J,1);