Browse Source

* TJSONIniFile class added

git-svn-id: trunk@36913 -
michael 8 years ago
parent
commit
25e85d1772

+ 3 - 0
.gitattributes

@@ -2532,6 +2532,7 @@ packages/fcl-json/examples/confdemo.lpi svneol=native#text/plain
 packages/fcl-json/examples/confdemo.pp svneol=native#text/plain
 packages/fcl-json/examples/demoformat.pp svneol=native#text/plain
 packages/fcl-json/examples/demortti.pp svneol=native#text/plain
+packages/fcl-json/examples/ini2json.pp svneol=native#text/plain
 packages/fcl-json/examples/parsedemo.lpi svneol=native#text/plain
 packages/fcl-json/examples/parsedemo.pp svneol=native#text/plain
 packages/fcl-json/examples/simpledemo.lpi svneol=native#text/plain
@@ -2542,10 +2543,12 @@ packages/fcl-json/src/fpjson.pp svneol=native#text/plain
 packages/fcl-json/src/fpjsonrtti.pp svneol=native#text/plain
 packages/fcl-json/src/fpjsontopas.pp svneol=native#text/plain
 packages/fcl-json/src/jsonconf.pp svneol=native#text/plain
+packages/fcl-json/src/jsonini.pp svneol=native#text/plain
 packages/fcl-json/src/jsonparser.pp svneol=native#text/plain
 packages/fcl-json/src/jsonreader.pp svneol=native#text/plain
 packages/fcl-json/src/jsonscanner.pp svneol=native#text/plain
 packages/fcl-json/tests/jsonconftest.pp svneol=native#text/plain
+packages/fcl-json/tests/tcjsonini.pp svneol=native#text/plain
 packages/fcl-json/tests/tcjsontocode.pp svneol=native#text/plain
 packages/fcl-json/tests/testcomps.pp svneol=native#text/plain
 packages/fcl-json/tests/testjson.lpi svneol=native#text/plain

+ 22 - 0
packages/fcl-json/examples/ini2json.pp

@@ -0,0 +1,22 @@
+program ini2json;
+
+{$mode objfpc}
+{$h+1}
+
+uses sysutils,jsonini;
+
+var
+  fin,fout : string;
+
+begin
+  if (ParamCount<1) then
+    begin
+    Writeln('Usage : ',ExtractFileName(ParamStr(0)),' infile [outfile]');
+    Halt(1);
+    end;
+  Fin:=ParamStr(1);  
+  FOut:=ParamStr(2);  
+  If (Fout='') then
+    Fout:=ChangeFileExt(Fin,'.json');
+  TJSONIniFile.ConvertIni(Fin,Fout,False);  
+end.

+ 9 - 0
packages/fcl-json/fpmake.pp

@@ -76,10 +76,19 @@ begin
       AddUnit('jsonparser');
       end;
 
+    T:=P.Targets.AddUnit('jsonini.pp');
+    T.ResourceStrings:=true;
+    with T.Dependencies do
+      begin
+      AddUnit('fpjson');
+      AddUnit('jsonparser');
+     end;
+
     P.ExamplePath.Add('examples');
     T:=P.Targets.AddExampleProgram('confdemo.pp');
     T:=P.Targets.AddExampleProgram('parsedemo.pp');
     T:=P.Targets.AddExampleProgram('simpledemo.pp');
+    T:=P.Targets.AddExampleProgram('ini2json.pp');
 
     // simpledemo.lpi
     // confdemo.lpi

+ 548 - 0
packages/fcl-json/src/jsonini.pp

@@ -0,0 +1,548 @@
+unit jsonini;
+
+{$mode objfpc}
+{$h+}
+
+interface
+
+uses
+  Classes, SysUtils, inifiles, fpjson, jsonscanner, jsonparser, dateutils;
+
+type
+
+  { TJSONIniFile }
+
+  TJSONIniFile = class(TCustomIniFile)
+  Private
+    FJSON: TJSONObject;
+    FCacheUpdates: Boolean;
+    FDirty : Boolean;
+    FStream: TStream;
+    procedure SetCacheUpdates(const AValue: Boolean);
+  protected
+    Function GetRoot : TJSONObject;
+    Function GetSection(Const ASectionName : String; AllowCreate : Boolean) : TJSONObject;
+    Function GetKeyData(Const ASectionName,AKeyName : String) : TJSONData;
+    // Return true if an existing item was replaced
+    Function SetKeyData(Const ASectionName,AKeyName : String; AData : TJSONData) : Boolean;
+    procedure MaybeUpdateFile;
+    property Dirty : Boolean Read FDirty;
+  public
+    constructor Create(const AFileName: string; AOptions : TIniFileOptions = []); override; overload;
+    constructor Create(AStream: TStream; AOptions : TJSONOptions); overload;
+    destructor Destroy; override;
+    Class Procedure ConvertIni(Const AIniFile,AJSONFile : String; StringsOnly : Boolean = True);
+    function ReadString(const Section, Ident, Default: string): string; override;
+    function ReadInteger(const Section, Ident: string; Default: Longint): Longint; override;
+    function ReadInt64(const Section, Ident: string; Default: Int64): Int64; override;
+    function ReadBool(const Section, Ident: string; Default: Boolean): Boolean; override;
+    function ReadDate(const Section, Ident: string; Default: TDateTime): TDateTime; override;
+    function ReadDateTime(const Section, Ident: string; Default: TDateTime): TDateTime; override;
+    function ReadFloat(const Section, Ident: string; Default: Double): Double; override;
+    function ReadTime(const Section, Ident: string; Default: TDateTime): TDateTime; override;
+    procedure WriteString(const Section, Ident, Value: String); override;
+    procedure WriteDate(const Section, Ident: string; Value: TDateTime); override;
+    procedure WriteDateTime(const Section, Ident: string; Value: TDateTime); override;
+    procedure WriteFloat(const Section, Ident: string; Value: Double); override;
+    procedure WriteTime(const Section, Ident: string; Value: TDateTime); override;
+    procedure WriteInteger(const Section, Ident: string; Value: Longint); override;
+    procedure WriteInt64(const Section, Ident: string; Value: Int64); override;
+    procedure WriteBool(const Section, Ident: string; Value: Boolean); override;
+    procedure ReadSection(const Section: string; Strings: TStrings); override;
+    procedure ReadSections(Strings: TStrings); override;
+    procedure ReadSectionValues(const Section: string; Strings: TStrings; AOptions : TSectionValuesOptions = [svoIncludeInvalid]); overload; override;
+    procedure EraseSection(const Section: string); override;
+    procedure DeleteKey(const Section, Ident: String); override;
+    procedure UpdateFile; override; overload;
+    procedure UpdateFile(Const AFileName : string); overload;
+    property Stream: TStream read FStream;
+    property CacheUpdates : Boolean read FCacheUpdates write SetCacheUpdates;
+  end;
+
+implementation
+
+{ TJSONIniFile }
+
+procedure TJSONIniFile.SetCacheUpdates(const AValue: Boolean);
+begin
+  if FCacheUpdates and not AValue and FDirty then
+    UpdateFile;
+end;
+
+function TJSONIniFile.GetRoot: TJSONObject;
+begin
+  Result:=FJSON;
+end;
+
+function TJSONIniFile.GetSection(const ASectionName: String; AllowCreate: Boolean): TJSONObject;
+
+Var
+  I : Integer;
+  R : TJSONObject;
+
+begin
+  Result:=Nil;
+  R:=GetRoot;
+  I:=R.IndexOfName(ASectionName,True);
+  if (I<>-1) and (R.Items[i].JSONType=jtObject) then
+    Result:=R.Items[i] as TJSONObject
+  else if AllowCreate then
+    begin
+    if (I<>-1) then
+      R.Delete(I);
+    Result:=TJSONObject.Create;
+    R.Add(ASectionName,Result);
+    end;
+end;
+
+function TJSONIniFile.GetKeyData(const ASectionName, AKeyName: String): TJSONData;
+
+Var
+  O : TJSONObject;
+  I : integer;
+
+begin
+  Result:=Nil;
+  O:=GetSection(ASectionName,False);
+  if Assigned(O) then
+    begin
+    I:=O.IndexOfName(AKeyName,True);
+    if (I<>-1) and (O.Items[i].JSONType in ActualValueJSONTypes) then
+      Result:=O.Items[i];
+    end
+end;
+
+function TJSONIniFile.SetKeyData(const ASectionName, AKeyName: String; AData: TJSONData): Boolean;
+Var
+  O : TJSONObject;
+  I : integer;
+
+begin
+  O:=GetSection(ASectionName,true);
+  I:=O.IndexOfName(AKeyName,True);
+  Result:=(I<>-1);
+  if Result then
+    O.Delete(I);
+  O.Add(aKeyName,AData);
+  FDirty:=True;
+end;
+
+procedure TJSONIniFile.MaybeUpdateFile;
+begin
+  If FCacheUpdates then
+    FDirty:=True
+  else
+    UpdateFile;
+end;
+
+constructor TJSONIniFile.Create(const AFileName: string; AOptions : TIniFileOptions = []);
+
+Var
+  F : TFileStream;
+
+begin
+  Inherited Create(AFileName,AOptions);
+  if Not FileExists(AFileName) then
+    FJSON:=TJSONObject.Create
+  else
+    begin
+    F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
+    try
+      Create(F,[joUTF8,joComments,joIgnoreTrailingComma]);
+    finally
+      F.Free;
+    end;
+    end;
+end;
+
+constructor TJSONIniFile.Create(AStream: TStream; AOptions: TJSONOptions);
+
+Var
+  P : TJSONParser;
+  D : TJSONData;
+
+begin
+  D:=Nil;
+  P:=TJSONParser.Create(AStream,AOptions);
+  try
+    D:=P.Parse;
+    if (D is TJSONObject) then
+      begin
+      FJSON:=D as TJSONObject;
+      D:=Nil;
+      end
+    else
+      FJSON:=TJSONObject.Create;
+  finally
+    D.Free;
+    P.Free;
+  end;
+end;
+
+destructor TJSONIniFile.Destroy;
+begin
+  FreeAndNil(FJSON);
+  inherited Destroy;
+end;
+
+class procedure TJSONIniFile.ConvertIni(const AIniFile, AJSONFile: String; StringsOnly: Boolean = true);
+
+Var
+  SIni : TMemIniFile;
+  Dini : TJSONIniFile;
+  S,K : TStrings;
+  SN,KN,V : String;
+  I6 : Int64;
+  F : Double;
+  B : Boolean;
+  DT : TDateTime;
+
+begin
+  S:=Nil;
+  K:=Nil;
+  Dini:=Nil;
+  SIni:=TMemIniFile.Create(AIniFile);
+  try
+    DIni:=Self.Create(AJSONFile);
+    S:=TStringList.Create;
+    K:=TStringList.Create;
+    SIni.ReadSections(S);
+    For SN in S do
+      begin
+      SIni.ReadSection(SN,K);
+      For KN in K do
+        begin
+        V:=Sini.ReadString(SN,KN,'');
+        if StringsOnly then
+          Dini.WriteString(SN,KN,V)
+        else
+          begin
+          If TryStrToInt64(V,I6) then
+            Dini.WriteInt64(SN,KN,I6)
+          else If TryStrToFloat(V,F) then
+            Dini.WriteFloat(SN,KN,F)
+          else If TryStrToBool(V,B) then
+            Dini.WriteBool(SN,KN,B)
+          else
+            begin
+            DT:=SIni.ReadTime(SN,KN,-1);
+            B:=DT<>-1;
+            if B then
+              DIni.WriteTime(SN,KN,DT)
+            else
+              begin
+              DT:=SIni.ReadDate(SN,KN,0);
+              B:=DT<>0;
+              if B then
+                DIni.WriteDate(SN,KN,DT)
+              else
+                begin
+                DT:=SIni.ReadDateTime(SN,KN,0);
+                B:=DT<>0;
+                if B then
+                  DIni.WriteDateTime(SN,KN,DT)
+                end;
+              end;
+            if Not B then
+              Dini.WriteString(SN,KN,V)
+            end;
+          end;
+        end;
+      end;
+    Dini.UpdateFile;
+  finally
+    FreeAndNil(S);
+    FreeAndNil(K);
+    FreeAndNil(Dini);
+    FreeAndNil(Sini);
+  end;
+end;
+
+function TJSONIniFile.ReadString(const Section, Ident, Default: string): string;
+
+Var
+  D : TJSONData;
+
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else
+    begin
+    if D.JSONType in StructuredJSONTypes then
+      Result:=D.AsJSON
+    else
+      Result:=D.AsString;
+    end
+end;
+
+function TJSONIniFile.ReadInteger(const Section, Ident: string; Default: Longint): Longint;
+
+Var
+  D : TJSONData;
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else
+    if D.JSONType=jtNumber then
+      Result:=D.AsInteger
+    else
+      if not TryStrToInt(D.AsString,Result) then
+        Result:=Default;
+end;
+
+function TJSONIniFile.ReadInt64(const Section, Ident: string; Default: Int64): Int64;
+
+Var
+  D : TJSONData;
+
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else
+    if D.JSONType=jtNumber then
+      Result:=D.AsInt64
+    else
+      if not TryStrToInt64(D.AsString,Result) then
+        Result:=Default;
+end;
+
+function TJSONIniFile.ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
+
+Var
+  D : TJSONData;
+
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else
+    // Avoid exception frame
+    if D.JSONType=jtBoolean then
+      Result:=D.AsBoolean
+    else
+      try
+        Result:=D.AsBoolean;
+      except
+        Result:=Default;
+      end;
+end;
+
+function TJSONIniFile.ReadDate(const Section, Ident: string; Default: TDateTime): TDateTime;
+
+Var
+  D : TJSONData;
+
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else if D.JSONType=jtNumber then
+    Result:=TDateTime(D.AsFloat)
+  else
+    Result:=ScanDateTime('yyyy"-"mm"-"dd',D.AsString);
+end;
+
+function TJSONIniFile.ReadDateTime(const Section, Ident: string; Default: TDateTime): TDateTime;
+Var
+  D : TJSONData;
+
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else if D.JSONType=jtNumber then
+    Result:=TDateTime(D.AsFloat)
+  else
+    Result:=ScanDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss"."zzz',D.AsString);
+end;
+
+function TJSONIniFile.ReadFloat(const Section, Ident: string; Default: Double): Double;
+Var
+  D : TJSONData;
+  C : Integer;
+
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else
+    if D.JSONType=jtNumber then
+      Result:=D.AsFloat
+    else
+      // Localized
+      if not TryStrToFloat(D.AsString,Result) then
+        begin
+        // Not localized
+        Val(D.AsString,Result,C);
+        if (C<>0) then
+          Result:=Default;
+        end;
+end;
+
+function TJSONIniFile.ReadTime(const Section, Ident: string; Default: TDateTime): TDateTime;
+
+Var
+  D : TJSONData;
+
+begin
+  D:=GetKeyData(Section,Ident);
+  if Not Assigned(D) then
+    Result:=Default
+  else if D.JSONType=jtNumber then
+    Result:=Frac(TDateTime(D.AsFloat))
+  else
+    Result:=ScanDateTime('"0000-00-00T"hh":"nn":"ss"."zzz',D.AsString);
+end;
+
+procedure TJSONIniFile.WriteString(const Section, Ident, Value: String);
+begin
+  SetKeyData(Section,Ident,CreateJSON(Value));
+end;
+
+procedure TJSONIniFile.WriteDate(const Section, Ident: string; Value: TDateTime);
+begin
+  SetKeyData(Section,Ident,CreateJSON(FormatDateTime('yyyy"-"mm"-"dd"T"00":"00":"00.zzz',Value)));
+end;
+
+procedure TJSONIniFile.WriteDateTime(const Section, Ident: string; Value: TDateTime);
+begin
+  SetKeyData(Section,Ident,CreateJSON(FormatDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss.zzz',Value)));
+end;
+
+procedure TJSONIniFile.WriteFloat(const Section, Ident: string; Value: Double);
+begin
+  SetKeyData(Section,Ident,CreateJSON(Value));
+end;
+
+procedure TJSONIniFile.WriteTime(const Section, Ident: string; Value: TDateTime);
+begin
+  SetKeyData(Section,Ident,CreateJSON(FormatDateTime('0000"-"00"-"00"T"hh":"nn":"ss.zzz',Value)));
+end;
+
+procedure TJSONIniFile.WriteInteger(const Section, Ident: string; Value: Longint);
+begin
+  SetKeyData(Section,Ident,CreateJSON(Value));
+end;
+
+procedure TJSONIniFile.WriteInt64(const Section, Ident: string; Value: Int64);
+begin
+  SetKeyData(Section,Ident,CreateJSON(Value));
+end;
+
+procedure TJSONIniFile.WriteBool(const Section, Ident: string; Value: Boolean);
+begin
+  SetKeyData(Section,Ident,CreateJSON(Value));
+end;
+
+procedure TJSONIniFile.ReadSection(const Section: string; Strings: TStrings);
+Var
+  O : TJSONObject;
+  E : TJSONEnum;
+
+begin
+  O:=GetSection(Section,False);
+  if Assigned(O) then
+    For E in O do
+      If (E.Value.JSONType in ActualValueJSONTypes) then
+        Strings.Add(E.Key);
+end;
+
+procedure TJSONIniFile.ReadSections(Strings: TStrings);
+
+Var
+  R : TJSONObject;
+  E : TJSONEnum;
+
+begin
+  R:=GetRoot;
+  for E in R do
+    if E.Value.JSONType=jtObject then
+      Strings.Add(E.Key);
+end;
+
+procedure TJSONIniFile.ReadSectionValues(const Section: string; Strings: TStrings; AOptions: TSectionValuesOptions);
+
+Var
+  O : TJSONObject;
+  E : TJSONEnum;
+  V : TJSONStringType;
+
+begin
+  O:=GetSection(Section,False);
+  if Assigned(O) then
+    For E in O do
+      begin
+      If (E.Value.JSONType in ActualValueJSONTypes) then
+        begin
+        V:=E.Value.AsString;
+        Strings.Add(E.Key+'='+V);
+        end
+      else if (svoIncludeInvalid in AOptions) then
+        begin
+        V:=E.Value.AsJSON;
+        Strings.Add(E.Key+'='+V);
+        end
+      end;
+end;
+
+procedure TJSONIniFile.EraseSection(const Section: string);
+
+Var
+  I : Integer;
+
+begin
+  I:=GetRoot.IndexOfName(Section,True);
+  if (I<>-1) then
+    begin
+    GetRoot.Delete(I);
+    MaybeUpdateFile;
+    end;
+end;
+
+procedure TJSONIniFile.DeleteKey(const Section, Ident: String);
+
+Var
+  O : TJSONObject;
+  I : integer;
+
+begin
+  O:=GetSection(Section,False);
+  if O<>Nil then
+    begin
+    I:=O.IndexOfName(Ident,True);
+    if I<>-1 then
+      begin
+      O.Delete(I);
+      MaybeUpdateFile;
+      end;
+    end;
+end;
+
+procedure TJSONIniFile.UpdateFile;
+
+
+begin
+  If (FileName<>'') then
+    UpdateFile(FileName)
+end;
+
+procedure TJSONIniFile.UpdateFile(const AFileName: string);
+
+Var
+  S : TJSONStringType;
+
+begin
+  With TFileStream.Create(AFileName,fmCreate) do
+    try
+      S:=FJSON.FormatJSON();
+      WriteBuffer(S[1],Length(S));
+    finally
+      Free;
+    end;
+end;
+
+end.
+

+ 620 - 0
packages/fcl-json/tests/tcjsonini.pp

@@ -0,0 +1,620 @@
+unit tcjsonini;
+
+{$mode objfpc}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry, fpjson, inifiles, jsonini;
+
+Type
+
+  { TJSONIniTest }
+
+  TJSONIniTest = Class(TTestCase)
+  private
+    FFileContent: TJSONData;
+    Fini: TJSONIniFile;
+    FStrings: TStrings;
+    FTestFile: String;
+    procedure AssertValue(const aSection, Akey, avalue: string);
+    procedure CreateIni;
+    function GetIni: TJSONIniFile;
+    function GetO: TJSONObject;
+  Protected
+    procedure HaveFile;
+    Procedure ReLoad;
+    procedure NoFileYet;
+    procedure RemoveFile;
+    Procedure Setup; override;
+    Procedure TearDown; override;
+    Procedure ReadFile;
+    Procedure WriteFile;
+    Procedure SampleFile;
+    Property TestFile : String Read FTestFile;
+    Property FileContent : TJSONData Read FFileContent Write FFileContent;
+    Property ObjFileContent : TJSONObject Read GetO;
+    Property Ini : TJSONIniFile Read GetIni;
+    Property Strings : TStrings Read FStrings;
+  Published
+    Procedure TestEmpty;
+    Procedure TestReadEmpty;
+    Procedure TestReadEmptyValue;
+    Procedure TestReadEmptyObject;
+    Procedure TestRead1EmptySection;
+    Procedure TestReadSections;
+    procedure TestReadSection;
+    procedure TestReadSectionValues;
+    Procedure TestReadString;
+    Procedure TestReadInteger;
+    Procedure TestReadInt64;
+    Procedure TestReadFloat;
+    Procedure TestReadBoolean;
+    Procedure TestReadDate;
+    Procedure TestReadTime;
+    Procedure TestReadDateTime;
+    Procedure TestEraseSection;
+    Procedure TestEraseSectionCaseMismatch;
+    Procedure TestDeleteKey;
+    Procedure TestDeleteKeySectionCaseMismatch;
+    Procedure TestDeleteKeyKeyCaseMismatch;
+    Procedure TestWriteString;
+    Procedure TestWriteInteger;
+    Procedure TestWriteBoolean;
+    Procedure TestWriteDate;
+    Procedure TestWriteDateTime;
+    Procedure TestWriteTime;
+    Procedure TestConvertIni;
+    Procedure TestConvertIniString;
+  end;
+
+implementation
+
+{ TJSONIniTest }
+
+function TJSONIniTest.GetIni: TJSONIniFile;
+begin
+  If FIni=Nil then
+    begin
+    Fini:=TJSONIniFile.Create(TestFile);
+    end;
+  Result:=FIni;
+end;
+
+function TJSONIniTest.GetO: TJSONObject;
+begin
+  Result:=FFileContent as TJSONObject;
+end;
+
+procedure TJSONIniTest.Setup;
+begin
+  Inherited;
+  FTestFile:=TestName+'.json';
+  If FileExists(FTestFile) then
+    DeleteFile(FTestFile);
+  FStrings:=TStringList.Create;
+  // Do nothing
+end;
+
+procedure TJSONIniTest.TearDown;
+begin
+  If FileExists(FTestFile) then
+    DeleteFile(FTestFile);
+  FreeAndNil(FFileContent);
+  FreeAndNil(FIni);
+  FreeAndNil(FStrings);
+  Inherited;
+end;
+
+procedure TJSONIniTest.ReadFile;
+
+Var
+  F : TFileStream;
+
+begin
+  FreeAndNil(FFileContent);
+  AssertTrue('Test File '+TestFile+' exists.',FileExists(TestFile));
+  F:=TFileStream.Create(TestFile,fmOpenRead or fmShareDenyWrite);
+  try
+    FileContent:=GetJSON(F);
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TJSONIniTest.WriteFile;
+
+Var
+  F : TFileStream;
+  S : TJSONStringType;
+
+begin
+  F:=TFileStream.Create(TestFile,fmCreate);
+  try
+    S:=FFileContent.AsJSON;
+    F.WriteBuffer(S[1],Length(S));
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TJSONIniTest.SampleFile;
+
+begin
+  FileContent:=TJSONObject.Create([
+    'a',TJSONObject.Create([
+      'i',1,
+      'i6',TJSONInt64Number.Create(Maxint*2),
+      'f',1.2,
+      's','test',
+      'si','1',
+      'si6',IntToStr(int64(MaxInt*2)),
+      'sf','1.2',
+      'dt','2001-05-06T23:24:25.678',
+      'id',Round(EncodeDate(2001,05,06)),
+      'fd',EncodeDate(2001,05,06),
+      't','0000-00-00T12:13:14.567',
+      'ft',Frac(EncodeTime(12,13,14,567)),
+      'fdt',EncodeDate(2001,05,06)+EncodeTime(23,24,25,678),
+      'd','2001-05-06',
+      'b',true,
+      'n',Nil,
+      'o',TJSONObject.Create
+    ]),
+    'B',TJSONObject.Create([
+      'I',1,
+      'F',1.2,
+      'S','test',
+      'SI','1',
+      'SF','1.2',
+      'DT','2001-05-06T23:24:25.678',
+      'T','0000-00-00T12:13:14.567',
+      'D','2001-05-06',
+      'B',true,
+      'N',Nil,
+      'O',TJSONObject.Create
+    ]),
+    'NO','not'
+  ]);
+  WriteFile;
+end;
+
+procedure TJSONIniTest.TestEmpty;
+begin
+  AssertFalse('No test file',FileExists(testfile));
+  AssertNull('No ini',Fini);
+  AssertNull('No file content',FFileContent);
+  AssertNotNull('Have strings',Strings);
+  AssertEquals('Have empty strings',0,Strings.Count);
+end;
+
+procedure TJSONIniTest.TestReadEmpty;
+begin
+  Ini.ReadSections(Strings);
+  AssertEquals('No sections',0,Strings.Count);
+end;
+
+procedure TJSONIniTest.TestReadEmptyValue;
+begin
+  FileContent:=TJSONString.Create('me');
+  WriteFile;
+  Ini.ReadSections(Strings);
+  AssertEquals('No sections',0,Strings.Count);
+end;
+
+procedure TJSONIniTest.TestReadEmptyObject;
+begin
+  FileContent:=TJSONObject.Create();
+  WriteFile;
+  Ini.ReadSections(Strings);
+  AssertEquals('No sections',0,Strings.Count);
+end;
+
+procedure TJSONIniTest.TestRead1EmptySection;
+begin
+  FileContent:=TJSONObject.Create(['empty',TJSONOBject.Create]);
+  WriteFile;
+  Ini.ReadSections(Strings);
+  AssertEquals('1 sections',1,Strings.Count);
+  AssertEquals('Section name','empty',Strings[0]);
+end;
+
+procedure TJSONIniTest.TestReadSections;
+begin
+  SampleFile;
+  Ini.ReadSections(Strings);
+  AssertEquals('2 sections',2,Strings.Count);
+  AssertEquals('Section name 0','a',Strings[0]);
+  AssertEquals('Section name 1','B',Strings[1]);
+end;
+
+procedure TJSONIniTest.TestReadSection;
+begin
+  SampleFile;
+  Ini.ReadSection('a',Strings);
+  // Only valid values are reported
+  AssertEquals('value count',(FileContent as TJSONObject).Objects['a'].Count-2,Strings.Count);
+  AssertEquals('value names','i,i6,f,s,si,si6,sf,dt,id,fd,t,ft,fdt,d,b',Strings.CommaText);
+end;
+
+procedure TJSONIniTest.TestReadSectionValues;
+
+Var
+  D : TJSONEnum;
+
+begin
+  SampleFile;
+  Ini.ReadSectionValues('a',Strings,[]);
+  // Only valid values are reported
+  AssertEquals('value count',(FileContent as TJSONObject).Objects['a'].Count-2,Strings.Count);
+  for D in (FileContent as TJSONObject).Objects['a'] do
+    if D.Value.JSONType in ActualValueJSONTypes then
+      AssertEquals('value '+D.key,D.Value.AsString,Strings.Values[D.Key]);
+  Strings.Clear;
+  Ini.ReadSectionValues('a',Strings);
+  // All valid values are reported
+  AssertEquals('value count',(FileContent as TJSONObject).Objects['a'].Count,Strings.Count);
+end;
+
+procedure TJSONIniTest.TestReadString;
+begin
+  SampleFile;
+  AssertEquals('Value, case OK','test',Ini.ReadString('a','s','nono'));
+  AssertEquals('Value, key case not OK','test',Ini.ReadString('a','S','nono'));
+  AssertEquals('Value, section case not OK','test',Ini.ReadString('A','s','nono'));
+  AssertEquals('Value, section not exist','nono',Ini.ReadString('C','s','nono'));
+  AssertEquals('Value, key not exist','nono',Ini.ReadString('a','Z','nono'));
+  AssertEquals('Value, key not string','1',Ini.ReadString('a','i','nono'));
+  AssertEquals('Value, key not valid value','nono',Ini.ReadString('a','o','nono'));
+end;
+
+procedure TJSONIniTest.TestReadInteger;
+
+begin
+  SampleFile;
+  AssertEquals('Value, case OK',1,Ini.ReadInteger('a','i',2));
+  AssertEquals('Value, key case not OK',1,Ini.ReadInteger('a','I',2));
+  AssertEquals('Value, section case not OK',1,Ini.ReadInteger('A','i',2));
+  AssertEquals('Value, section not exist',2,Ini.ReadInteger('C','i',2));
+  AssertEquals('Value, key not exist',2,Ini.ReadInteger('a','Z',2));
+  AssertEquals('Value, key not integer',2,Ini.ReadInteger('a','s',2));
+  AssertEquals('Value, key not integer, but convertable to integer',1,Ini.ReadInteger('a','si',2));
+end;
+
+procedure TJSONIniTest.TestReadInt64;
+Var
+  I6 : Int64;
+begin
+  I6:=MaxInt*2;
+  SampleFile;
+  AssertEquals('Value, case OK',i6,Ini.ReadInt64('a','i6',2));
+  AssertEquals('Value, key case not OK',i6,Ini.ReadInt64('a','I6',2));
+  AssertEquals('Value, section case not OK',i6,Ini.ReadInt64('A','i6',2));
+  AssertEquals('Value, section not exist',2,Ini.ReadInt64('C','i',2));
+  AssertEquals('Value, key not exist',2,Ini.ReadInt64('a','Z',2));
+  AssertEquals('Value, key not integer',2,Ini.ReadInt64('a','s',2));
+  AssertEquals('Value, key not integer, but convertable to int64',I6,Ini.ReadInt64('a','si6',2));
+end;
+
+procedure TJSONIniTest.TestReadFloat;
+begin
+  SampleFile;
+  AssertEquals('Value, case OK',1.2,Ini.ReadFloat('a','f',2.3));
+  AssertEquals('Value, key case not OK',1.2,Ini.ReadFloat('a','F',2.3));
+  AssertEquals('Value, section case not OK',1.2,Ini.ReadFloat('A','f',2.3));
+  AssertEquals('Value, section not exist',2.3,Ini.ReadFloat('C','f',2.3));
+  AssertEquals('Value, key not exist',2.3,Ini.ReadFloat('a','Z',2.3));
+  AssertEquals('Value, key not float',2.3,Ini.ReadFloat('a','s',2.3));
+  AssertEquals('Value, key not float, but convertable to float',1.2,Ini.ReadFloat('a','sf',2.3));
+end;
+
+procedure TJSONIniTest.TestReadBoolean;
+begin
+  SampleFile;
+  AssertEquals('Value, case OK',True,Ini.ReadBool('a','b',False));
+  AssertEquals('Value, key case not OK',True,Ini.ReadBool('a','B',False));
+  AssertEquals('Value, section case not OK',True,Ini.ReadBool('A','b',False));
+  AssertEquals('Value, section not exist',True,Ini.ReadBool('C','b',True));
+  AssertEquals('Value, key not exist',True,Ini.ReadBool('a','Z',True));
+  AssertEquals('Value, key not bool but integer',True,Ini.ReadBool('a','i',false));
+end;
+
+procedure TJSONIniTest.TestReadDate;
+
+Var
+  D,DD : TDateTime;
+
+begin
+  D:=EncodeDate(2001,05,06);
+  DD:=EncodeDate(1999,11,12);
+  SampleFile;
+  AssertEquals('Value, case OK',D,Ini.ReadDate('a','d',DD));
+  AssertEquals('Value, key case not OK',D,Ini.ReadDate('a','D',DD));
+  AssertEquals('Value, section case not OK',D,Ini.ReadDate('A','d',DD));
+  AssertEquals('Value, section not exist',DD,Ini.ReadDate('C','d',DD));
+  AssertEquals('Value, date as integer',D,Ini.ReadDate('a','id',DD));
+  AssertEquals('Value, date as float',D,Ini.ReadDate('a','fd',DD));
+end;
+
+procedure TJSONIniTest.TestReadTime;
+
+Var
+  T,DT : TDateTime;
+
+begin
+  T:=EncodeTime(12,13,14,567);
+  DT:=EncodeTime(1,2,3,4);
+  SampleFile;
+  AssertEquals('Value, case OK',T,Ini.ReadTime('a','t',DT));
+  AssertEquals('Value, key case not OK',T,Ini.ReadTime('a','T',DT));
+  AssertEquals('Value, section case not OK',T,Ini.ReadTime('A','t',DT));
+  AssertEquals('Value, section not exist',DT,Ini.ReadTime('C','t',DT));
+  AssertEquals('Value, key exist as float',T,Ini.ReadTime('a','ft',DT));
+end;
+
+procedure TJSONIniTest.TestReadDateTime;
+Var
+  DT,DDT : TDateTime;
+
+begin
+  DT:=EncodeDate(2001,05,06)+EncodeTime(23,24,25,678);
+  DDT:=EncodeDate(1999,11,12)+EncodeTime(1,2,3,4);
+  SampleFile;
+  AssertEquals('Value, case OK',DT,Ini.ReadDateTime('a','dt',DDT));
+  AssertEquals('Value, key case not OK',DT,Ini.ReadDateTime('a','DT',DDT));
+  AssertEquals('Value, section case not OK',DT,Ini.ReadDateTime('A','dt',DDT));
+  AssertEquals('Value, section not exist',DDT,Ini.ReadDateTime('C','dt',DDT));
+  AssertEquals('Value, key exist as float',DT,Ini.ReadDateTime('a','fdt',DDT));
+end;
+
+procedure TJSONIniTest.TestEraseSection;
+begin
+  SampleFile;
+  Ini.EraseSection('B');
+  Ini.UpdateFile;
+  ReadFile;
+  AssertEquals('No more section',-1,ObjFileContent.IndexOfName('B'));
+end;
+
+procedure TJSONIniTest.TestEraseSectionCaseMismatch;
+begin
+  SampleFile;
+  Ini.EraseSection('b');
+  Ini.UpdateFile;
+  ReadFile;
+  AssertEquals('No more section',-1,ObjFileContent.IndexOfName('B'));
+end;
+
+procedure TJSONIniTest.TestDeleteKey;
+begin
+  SampleFile;
+  Ini.DeleteKey('a','i');
+  Ini.UpdateFile;
+  ReadFile;
+  AssertEquals('No more key',-1,ObjFileContent.Objects['a'].IndexOfName('i'));
+end;
+
+procedure TJSONIniTest.TestDeleteKeySectionCaseMismatch;
+begin
+  SampleFile;
+  Ini.DeleteKey('A','i');
+  Ini.UpdateFile;
+  ReadFile;
+  AssertEquals('No more key',-1,ObjFileContent.Objects['a'].IndexOfName('i'));
+end;
+
+procedure TJSONIniTest.TestDeleteKeyKeyCaseMismatch;
+begin
+  SampleFile;
+  Ini.DeleteKey('a','I');
+  Ini.UpdateFile;
+  ReadFile;
+  AssertEquals('No more key',-1,ObjFileContent.Objects['a'].IndexOfName('i'));
+end;
+
+procedure TJSONIniTest.AssertValue(const aSection,Akey,avalue : string);
+
+Var
+  D : TJSONData;
+
+begin
+  ini.UpdateFile;
+  ReadFile;
+  D:=ObjFileContent.FindPath(asection+'.'+akey);
+  AssertNotNull('Have value at '+asection+'.'+akey,D);
+  AssertEquals('Correct value at '+asection+'.'+akey,AValue,D.AsString);
+end;
+
+procedure TJSONIniTest.NoFileYet;
+
+begin
+  AssertFalse('File not exist yet',FileExists(TestFile));
+end;
+
+procedure TJSONIniTest.HaveFile;
+
+begin
+  AssertTrue('Test file exists',FileExists(TestFile));
+end;
+
+procedure TJSONIniTest.ReLoad;
+begin
+  FreeAndNil(Fini);
+  AssertNotNull(Ini);
+end;
+
+procedure TJSONIniTest.RemoveFile;
+begin
+  if FileExists(TestFile) then
+    AssertTrue('Deleted file',DeleteFile(TestFile));
+end;
+
+procedure TJSONIniTest.TestWriteString;
+begin
+  Ini.WriteString('a','i','string');
+  NoFileYet;
+  AssertValue('a','i','string');
+  Ini.CacheUpdates:=False;
+  Ini.WriteString('a','i','string2');
+  HaveFile;
+  AssertValue('a','i','string2');
+  Reload;
+  AssertEquals('Can read value','string2',Ini.ReadString('a','i',''));
+end;
+
+procedure TJSONIniTest.TestWriteInteger;
+begin
+  Ini.Writeinteger('a','i',2);
+  NoFileYet;
+  AssertValue('a','i','2');
+  Ini.CacheUpdates:=False;
+  Ini.WriteInteger('a','i',3);
+  HaveFile;
+  AssertValue('a','i','3');
+  Reload;
+  AssertEquals('Can read value',3,Ini.ReadInteger('a','i',0));
+end;
+
+procedure TJSONIniTest.TestWriteBoolean;
+begin
+  Ini.WriteBool('a','i',true);
+  NoFileYet;
+  AssertValue('a','i','True');
+  Ini.CacheUpdates:=False;
+  Ini.WriteBool('a','i2',true);
+  HaveFile;
+  AssertValue('a','i2','True');
+  Reload;
+  AssertEquals('Can read value',True,Ini.ReadBool('a','i2',false));
+end;
+
+procedure TJSONIniTest.TestWriteDate;
+Var
+  D : TDateTime;
+begin
+  D:=EncodeDate(2001,2,3);
+  Ini.WriteDate('a','i',D);
+  NoFileYet;
+  AssertValue('a','i','2001-02-03T00:00:00.000');
+  Ini.CacheUpdates:=False;
+  Ini.WriteDate('a','i',D+1);
+  HaveFile;
+  AssertValue('a','i','2001-02-04T00:00:00.000');
+  Reload;
+  AssertEquals('Can read value',D+1,Ini.ReadDate('a','i',0));
+end;
+
+procedure TJSONIniTest.TestWriteDateTime;
+
+Var
+  D : TDateTime;
+
+begin
+  D:=EncodeDate(2001,2,3)+EncodeTime(12,13,14,567);
+  Ini.WriteDateTime('a','i',D);
+  NoFileYet;
+  AssertValue('a','i','2001-02-03T12:13:14.567');
+  Ini.CacheUpdates:=False;
+  Ini.WriteDateTime('a','i',D+1);
+  HaveFile;
+  AssertValue('a','i','2001-02-04T12:13:14.567');
+  Reload;
+  AssertEquals('Can read value',D+1,Ini.ReadDateTime('a','i',0));
+end;
+
+procedure TJSONIniTest.TestWriteTime;
+
+Var
+  D,D2 : TDateTime;
+
+begin
+  D:=EncodeTime(12,13,14,567);
+  D2:=EncodeTime(13,14,15,678);
+  Ini.WriteTime('a','i',D);
+  NoFileYet;
+  AssertValue('a','i','0000-00-00T12:13:14.567');
+  Ini.CacheUpdates:=False;
+  Ini.WriteTime('a','i',D2);
+  HaveFile;
+  AssertValue('a','i','0000-00-00T13:14:15.678');
+  Reload;
+  AssertEquals('Can read value',D2,Ini.ReadTime('a','i',0));
+end;
+
+procedure TJSONIniTest.CreateIni;
+
+Var
+  M : TMemIniFile;
+  D,DT,T : TDateTime;
+
+begin
+  D:=EncodeDate(2001,2,3);
+  T:=EncodeTime(12,13,14,567);
+  DT:=D+T;
+  if FileExists(TestName+'.ini') then
+    DeleteFile(TestName+'.ini');
+  M:=TMemIniFile.Create(TestName+'.ini');
+  try
+    M.WriteString('a','s','c');
+    M.WriteInteger('a','i',2);
+    M.WriteBool('a','b',True);
+    M.WriteInt64('a','i6',Maxint*2);
+    M.WriteDate('a','d',D);
+    M.WriteTime('a','t',T);
+    M.WriteDateTime('a','dt',DT);
+    M.WriteFloat('a','f',1.23);
+    M.UpdateFile;
+  finally
+    M.Free;
+  end;
+end;
+
+procedure TJSONIniTest.TestConvertIni;
+
+Var
+  D,DT,T : TDateTime;
+
+begin
+  D:=EncodeDate(2001,2,3);
+  T:=EncodeTime(12,13,14,567);
+  DT:=D+T;
+  CreateIni;
+  TJSONIniFile.ConvertIni(TestName+'.ini',TestFile,False);
+  AssertEquals('String','c',Ini.ReadString('a','s',''));
+  AssertEquals('Integer',2,Ini.ReadInteger('a','i',1));
+  AssertEquals('Bool',True,Ini.ReadBool('a','b',False));
+  AssertEquals('Int64',Int64(Maxint*2),Ini.ReadInt64('a','i6',Maxint*2));
+  AssertEquals('Date',D, Ini.ReadDate('a','d',0));
+  AssertEquals('Time',T,Ini.ReadTime('a','t',0));
+  AssertEquals('DateTime',DT,Ini.ReadDateTime('a','dt',0));
+  AssertEquals('Float',1.23,Ini.ReadFloat('a','f',0));
+  if FileExists(TestName+'.ini') then
+    DeleteFile(TestName+'.ini');
+end;
+
+procedure TJSONIniTest.TestConvertIniString;
+Var
+  D,DT,T : TDateTime;
+
+begin
+  D:=EncodeDate(2001,2,3);
+  T:=EncodeTime(12,13,14,567);
+  DT:=D+T;
+  CreateIni;
+  TJSONIniFile.ConvertIni(TestName+'.ini',TestFile,True);
+  AssertEquals('String','c',Ini.ReadString('a','s',''));
+  AssertEquals('Integer',2,Ini.ReadInteger('a','i',1));
+  AssertEquals('Bool',True,Ini.ReadBool('a','b',False));
+  AssertEquals('Int64',Int64(Maxint*2),Ini.ReadInt64('a','i6',Maxint*2));
+  AssertEquals('Date',DateToStr(D), Ini.ReadString('a','d',''));
+  AssertEquals('Time',TimeToStr(T),Ini.ReadString('a','t',''));
+  AssertEquals('DateTime',DateTimeToStr(DT),Ini.ReadString('a','dt',''));
+  AssertEquals('Float',1.23,Ini.ReadFloat('a','f',0));
+  if FileExists(TestName+'.ini') then
+    DeleteFile(TestName+'.ini');
+end;
+
+initialization
+  RegisterTest(TJSONIniTest);
+end.
+

+ 12 - 2
packages/fcl-json/tests/testjson.lpi

@@ -22,7 +22,7 @@
     <RunParams>
       <local>
         <FormatVersion Value="1"/>
-        <CommandLineParams Value="--suite=TTestParser.TestArray"/>
+        <CommandLineParams Value="--suite=TJSONIniTest.TestConvertIni"/>
         <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
       </local>
     </RunParams>
@@ -31,7 +31,7 @@
         <PackageName Value="FCL"/>
       </Item1>
     </RequiredPackages>
-    <Units Count="6">
+    <Units Count="8">
       <Unit0>
         <Filename Value="testjson.pp"/>
         <IsPartOfProject Value="True"/>
@@ -56,6 +56,14 @@
         <Filename Value="testjsonreader.pp"/>
         <IsPartOfProject Value="True"/>
       </Unit5>
+      <Unit6>
+        <Filename Value="../src/jsonini.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit6>
+      <Unit7>
+        <Filename Value="tcjsonini.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit7>
     </Units>
   </ProjectOptions>
   <CompilerOptions>
@@ -79,9 +87,11 @@
     <Exceptions Count="2">
       <Item1>
         <Name Value="EConvertError"/>
+        <Enabled Value="False"/>
       </Item1>
       <Item2>
         <Name Value="EJSON"/>
+        <Enabled Value="False"/>
       </Item2>
     </Exceptions>
   </Debugging>

+ 1 - 1
packages/fcl-json/tests/testjson.pp

@@ -17,7 +17,7 @@
 program testjson;
 
 uses
-  Classes, testjsondata, testjsonparser, testjsonrtti, consoletestrunner, testjsonreader;
+  Classes, testjsondata, testjsonparser, testjsonrtti, consoletestrunner, testjsonreader, jsonini, tcjsonini;
 
 type
   { TLazTestRunner }