1unit jsonini;
2
3{$mode objfpc}
4{$h+}
5
6interface
7
8uses
9  Classes, SysUtils, inifiles, fpjson, jsonscanner, jsonparser, dateutils;
10
11type
12
13  { TJSONIniFile }
14
15  TJSONIniFile = class(TCustomIniFile)
16  Private
17    FJSON: TJSONObject;
18    FCacheUpdates: Boolean;
19    FDirty : Boolean;
20    FStream: TStream;
21    procedure SetCacheUpdates(const AValue: Boolean);
22  protected
23    Function GetRoot : TJSONObject;
24    Function GetSection(Const ASectionName : String; AllowCreate : Boolean) : TJSONObject;
25    Function GetKeyData(Const ASectionName,AKeyName : String) : TJSONData;
26    // Return true if an existing item was replaced
27    Function SetKeyData(Const ASectionName,AKeyName : String; AData : TJSONData) : Boolean;
28    procedure MaybeUpdateFile;
29    property Dirty : Boolean Read FDirty;
30  public
31    constructor Create(const AFileName: string; AOptions : TIniFileOptions = []); override; overload;
32    constructor Create(AStream: TStream; AOptions : TJSONOptions); overload;
33    destructor Destroy; override;
34    Class Procedure ConvertIni(Const AIniFile,AJSONFile : String; StringsOnly : Boolean = True);
35    function ReadString(const Section, Ident, Default: string): string; override;
36    function ReadInteger(const Section, Ident: string; Default: Longint): Longint; override;
37    function ReadInt64(const Section, Ident: string; Default: Int64): Int64; override;
38    function ReadBool(const Section, Ident: string; Default: Boolean): Boolean; override;
39    function ReadDate(const Section, Ident: string; Default: TDateTime): TDateTime; override;
40    function ReadDateTime(const Section, Ident: string; Default: TDateTime): TDateTime; override;
41    function ReadFloat(const Section, Ident: string; Default: Double): Double; override;
42    function ReadTime(const Section, Ident: string; Default: TDateTime): TDateTime; override;
43    procedure WriteString(const Section, Ident, Value: String); override;
44    procedure WriteDate(const Section, Ident: string; Value: TDateTime); override;
45    procedure WriteDateTime(const Section, Ident: string; Value: TDateTime); override;
46    procedure WriteFloat(const Section, Ident: string; Value: Double); override;
47    procedure WriteTime(const Section, Ident: string; Value: TDateTime); override;
48    procedure WriteInteger(const Section, Ident: string; Value: Longint); override;
49    procedure WriteInt64(const Section, Ident: string; Value: Int64); override;
50    procedure WriteBool(const Section, Ident: string; Value: Boolean); override;
51    procedure ReadSection(const Section: string; Strings: TStrings); override;
52    procedure ReadSections(Strings: TStrings); override;
53    procedure ReadSectionValues(const Section: string; Strings: TStrings; AOptions : TSectionValuesOptions = [svoIncludeInvalid]); overload; override;
54    procedure EraseSection(const Section: string); override;
55    procedure DeleteKey(const Section, Ident: String); override;
56    procedure UpdateFile; override; overload;
57    procedure UpdateFile(Const AFileName : string); overload;
58    property Stream: TStream read FStream;
59    property CacheUpdates : Boolean read FCacheUpdates write SetCacheUpdates;
60  end;
61
62implementation
63
64{ TJSONIniFile }
65
66procedure TJSONIniFile.SetCacheUpdates(const AValue: Boolean);
67begin
68  if FCacheUpdates and not AValue and FDirty then
69    UpdateFile;
70end;
71
72function TJSONIniFile.GetRoot: TJSONObject;
73begin
74  Result:=FJSON;
75end;
76
77function TJSONIniFile.GetSection(const ASectionName: String; AllowCreate: Boolean): TJSONObject;
78
79Var
80  I : Integer;
81  R : TJSONObject;
82
83begin
84  Result:=Nil;
85  R:=GetRoot;
86  I:=R.IndexOfName(ASectionName,True);
87  if (I<>-1) and (R.Items[i].JSONType=jtObject) then
88    Result:=R.Items[i] as TJSONObject
89  else if AllowCreate then
90    begin
91    if (I<>-1) then
92      R.Delete(I);
93    Result:=TJSONObject.Create;
94    R.Add(ASectionName,Result);
95    end;
96end;
97
98function TJSONIniFile.GetKeyData(const ASectionName, AKeyName: String): TJSONData;
99
100Var
101  O : TJSONObject;
102  I : integer;
103
104begin
105  Result:=Nil;
106  O:=GetSection(ASectionName,False);
107  if Assigned(O) then
108    begin
109    I:=O.IndexOfName(AKeyName,True);
110    if (I<>-1) and (O.Items[i].JSONType in ActualValueJSONTypes) then
111      Result:=O.Items[i];
112    end
113end;
114
115function TJSONIniFile.SetKeyData(const ASectionName, AKeyName: String; AData: TJSONData): Boolean;
116Var
117  O : TJSONObject;
118  I : integer;
119
120begin
121  O:=GetSection(ASectionName,true);
122  I:=O.IndexOfName(AKeyName,True);
123  Result:=(I<>-1);
124  if Result then
125    O.Delete(I);
126  O.Add(aKeyName,AData);
127  FDirty:=True;
128end;
129
130procedure TJSONIniFile.MaybeUpdateFile;
131begin
132  If FCacheUpdates then
133    FDirty:=True
134  else
135    UpdateFile;
136end;
137
138constructor TJSONIniFile.Create(const AFileName: string; AOptions : TIniFileOptions = []);
139
140Var
141  F : TFileStream;
142
143begin
144  Inherited Create(AFileName,AOptions);
145  if Not FileExists(AFileName) then
146    FJSON:=TJSONObject.Create
147  else
148    begin
149    F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
150    try
151      Create(F,[joUTF8,joComments,joIgnoreTrailingComma]);
152    finally
153      F.Free;
154    end;
155    end;
156end;
157
158constructor TJSONIniFile.Create(AStream: TStream; AOptions: TJSONOptions);
159
160Var
161  P : TJSONParser;
162  D : TJSONData;
163
164begin
165  D:=Nil;
166  P:=TJSONParser.Create(AStream,AOptions);
167  try
168    D:=P.Parse;
169    if (D is TJSONObject) then
170      begin
171      FJSON:=D as TJSONObject;
172      D:=Nil;
173      end
174    else
175      FJSON:=TJSONObject.Create;
176  finally
177    D.Free;
178    P.Free;
179  end;
180end;
181
182destructor TJSONIniFile.Destroy;
183begin
184  FreeAndNil(FJSON);
185  inherited Destroy;
186end;
187
188class procedure TJSONIniFile.ConvertIni(const AIniFile, AJSONFile: String; StringsOnly: Boolean = true);
189
190Var
191  SIni : TMemIniFile;
192  Dini : TJSONIniFile;
193  S,K : TStrings;
194  SN,KN,V : String;
195  I6 : Int64;
196  F : Double;
197  B : Boolean;
198  DT : TDateTime;
199
200begin
201  S:=Nil;
202  K:=Nil;
203  Dini:=Nil;
204  SIni:=TMemIniFile.Create(AIniFile);
205  try
206    DIni:=Self.Create(AJSONFile);
207    S:=TStringList.Create;
208    K:=TStringList.Create;
209    SIni.ReadSections(S);
210    For SN in S do
211      begin
212      SIni.ReadSection(SN,K);
213      For KN in K do
214        begin
215        V:=Sini.ReadString(SN,KN,'');
216        if StringsOnly then
217          Dini.WriteString(SN,KN,V)
218        else
219          begin
220          If TryStrToInt64(V,I6) then
221            Dini.WriteInt64(SN,KN,I6)
222          else If TryStrToFloat(V,F) then
223            Dini.WriteFloat(SN,KN,F)
224          else If TryStrToBool(V,B) then
225            Dini.WriteBool(SN,KN,B)
226          else
227            begin
228            DT:=SIni.ReadTime(SN,KN,-1);
229            B:=DT<>-1;
230            if B then
231              DIni.WriteTime(SN,KN,DT)
232            else
233              begin
234              DT:=SIni.ReadDate(SN,KN,0);
235              B:=DT<>0;
236              if B then
237                DIni.WriteDate(SN,KN,DT)
238              else
239                begin
240                DT:=SIni.ReadDateTime(SN,KN,0);
241                B:=DT<>0;
242                if B then
243                  DIni.WriteDateTime(SN,KN,DT)
244                end;
245              end;
246            if Not B then
247              Dini.WriteString(SN,KN,V)
248            end;
249          end;
250        end;
251      end;
252    Dini.UpdateFile;
253  finally
254    FreeAndNil(S);
255    FreeAndNil(K);
256    FreeAndNil(Dini);
257    FreeAndNil(Sini);
258  end;
259end;
260
261function TJSONIniFile.ReadString(const Section, Ident, Default: string): string;
262
263Var
264  D : TJSONData;
265
266begin
267  D:=GetKeyData(Section,Ident);
268  if Not Assigned(D) then
269    Result:=Default
270  else
271    begin
272    if D.JSONType in StructuredJSONTypes then
273      Result:=D.AsJSON
274    else
275      Result:=D.AsString;
276    end
277end;
278
279function TJSONIniFile.ReadInteger(const Section, Ident: string; Default: Longint): Longint;
280
281Var
282  D : TJSONData;
283begin
284  D:=GetKeyData(Section,Ident);
285  if Not Assigned(D) then
286    Result:=Default
287  else
288    if D.JSONType=jtNumber then
289      Result:=D.AsInteger
290    else
291      if not TryStrToInt(D.AsString,Result) then
292        Result:=Default;
293end;
294
295function TJSONIniFile.ReadInt64(const Section, Ident: string; Default: Int64): Int64;
296
297Var
298  D : TJSONData;
299
300begin
301  D:=GetKeyData(Section,Ident);
302  if Not Assigned(D) then
303    Result:=Default
304  else
305    if D.JSONType=jtNumber then
306      Result:=D.AsInt64
307    else
308      if not TryStrToInt64(D.AsString,Result) then
309        Result:=Default;
310end;
311
312function TJSONIniFile.ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
313
314Var
315  D : TJSONData;
316
317begin
318  D:=GetKeyData(Section,Ident);
319  if Not Assigned(D) then
320    Result:=Default
321  else
322    // Avoid exception frame
323    if D.JSONType=jtBoolean then
324      Result:=D.AsBoolean
325    else
326      try
327        Result:=D.AsBoolean;
328      except
329        Result:=Default;
330      end;
331end;
332
333function TJSONIniFile.ReadDate(const Section, Ident: string; Default: TDateTime): TDateTime;
334
335Var
336  D : TJSONData;
337
338begin
339  D:=GetKeyData(Section,Ident);
340  if Not Assigned(D) then
341    Result:=Default
342  else if D.JSONType=jtNumber then
343    Result:=TDateTime(D.AsFloat)
344  else
345    Result:=ScanDateTime('yyyy"-"mm"-"dd',D.AsString);
346end;
347
348function TJSONIniFile.ReadDateTime(const Section, Ident: string; Default: TDateTime): TDateTime;
349Var
350  D : TJSONData;
351
352begin
353  D:=GetKeyData(Section,Ident);
354  if Not Assigned(D) then
355    Result:=Default
356  else if D.JSONType=jtNumber then
357    Result:=TDateTime(D.AsFloat)
358  else
359    Result:=ScanDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss"."zzz',D.AsString);
360end;
361
362function TJSONIniFile.ReadFloat(const Section, Ident: string; Default: Double): Double;
363Var
364  D : TJSONData;
365  C : Integer;
366
367begin
368  D:=GetKeyData(Section,Ident);
369  if Not Assigned(D) then
370    Result:=Default
371  else
372    if D.JSONType=jtNumber then
373      Result:=D.AsFloat
374    else
375      // Localized
376      if not TryStrToFloat(D.AsString,Result) then
377        begin
378        // Not localized
379        Val(D.AsString,Result,C);
380        if (C<>0) then
381          Result:=Default;
382        end;
383end;
384
385function TJSONIniFile.ReadTime(const Section, Ident: string; Default: TDateTime): TDateTime;
386
387Var
388  D : TJSONData;
389
390begin
391  D:=GetKeyData(Section,Ident);
392  if Not Assigned(D) then
393    Result:=Default
394  else if D.JSONType=jtNumber then
395    Result:=Frac(TDateTime(D.AsFloat))
396  else
397    Result:=ScanDateTime('"0000-00-00T"hh":"nn":"ss"."zzz',D.AsString);
398end;
399
400procedure TJSONIniFile.WriteString(const Section, Ident, Value: String);
401begin
402  SetKeyData(Section,Ident,CreateJSON(Value));
403end;
404
405procedure TJSONIniFile.WriteDate(const Section, Ident: string; Value: TDateTime);
406begin
407  SetKeyData(Section,Ident,CreateJSON(FormatDateTime('yyyy"-"mm"-"dd"T"00":"00":"00.zzz',Value)));
408end;
409
410procedure TJSONIniFile.WriteDateTime(const Section, Ident: string; Value: TDateTime);
411begin
412  SetKeyData(Section,Ident,CreateJSON(FormatDateTime('yyyy"-"mm"-"dd"T"hh":"nn":"ss.zzz',Value)));
413end;
414
415procedure TJSONIniFile.WriteFloat(const Section, Ident: string; Value: Double);
416begin
417  SetKeyData(Section,Ident,CreateJSON(Value));
418end;
419
420procedure TJSONIniFile.WriteTime(const Section, Ident: string; Value: TDateTime);
421begin
422  SetKeyData(Section,Ident,CreateJSON(FormatDateTime('0000"-"00"-"00"T"hh":"nn":"ss.zzz',Value)));
423end;
424
425procedure TJSONIniFile.WriteInteger(const Section, Ident: string; Value: Longint);
426begin
427  SetKeyData(Section,Ident,CreateJSON(Value));
428end;
429
430procedure TJSONIniFile.WriteInt64(const Section, Ident: string; Value: Int64);
431begin
432  SetKeyData(Section,Ident,CreateJSON(Value));
433end;
434
435procedure TJSONIniFile.WriteBool(const Section, Ident: string; Value: Boolean);
436begin
437  SetKeyData(Section,Ident,CreateJSON(Value));
438end;
439
440procedure TJSONIniFile.ReadSection(const Section: string; Strings: TStrings);
441Var
442  O : TJSONObject;
443  E : TJSONEnum;
444
445begin
446  O:=GetSection(Section,False);
447  if Assigned(O) then
448    For E in O do
449      If (E.Value.JSONType in ActualValueJSONTypes) then
450        Strings.Add(E.Key);
451end;
452
453procedure TJSONIniFile.ReadSections(Strings: TStrings);
454
455Var
456  R : TJSONObject;
457  E : TJSONEnum;
458
459begin
460  R:=GetRoot;
461  for E in R do
462    if E.Value.JSONType=jtObject then
463      Strings.Add(E.Key);
464end;
465
466procedure TJSONIniFile.ReadSectionValues(const Section: string; Strings: TStrings; AOptions: TSectionValuesOptions);
467
468Var
469  O : TJSONObject;
470  E : TJSONEnum;
471  V : TJSONStringType;
472
473begin
474  O:=GetSection(Section,False);
475  if Assigned(O) then
476    For E in O do
477      begin
478      If (E.Value.JSONType in ActualValueJSONTypes) then
479        begin
480        V:=E.Value.AsString;
481        Strings.Add(E.Key+'='+V);
482        end
483      else if (svoIncludeInvalid in AOptions) then
484        begin
485        V:=E.Value.AsJSON;
486        Strings.Add(E.Key+'='+V);
487        end
488      end;
489end;
490
491procedure TJSONIniFile.EraseSection(const Section: string);
492
493Var
494  I : Integer;
495
496begin
497  I:=GetRoot.IndexOfName(Section,True);
498  if (I<>-1) then
499    begin
500    GetRoot.Delete(I);
501    MaybeUpdateFile;
502    end;
503end;
504
505procedure TJSONIniFile.DeleteKey(const Section, Ident: String);
506
507Var
508  O : TJSONObject;
509  I : integer;
510
511begin
512  O:=GetSection(Section,False);
513  if O<>Nil then
514    begin
515    I:=O.IndexOfName(Ident,True);
516    if I<>-1 then
517      begin
518      O.Delete(I);
519      MaybeUpdateFile;
520      end;
521    end;
522end;
523
524procedure TJSONIniFile.UpdateFile;
525
526
527begin
528  If (FileName<>'') then
529    UpdateFile(FileName)
530end;
531
532procedure TJSONIniFile.UpdateFile(const AFileName: string);
533
534Var
535  S : TJSONStringType;
536
537begin
538  With TFileStream.Create(AFileName,fmCreate) do
539    try
540      S:=FJSON.FormatJSON();
541      WriteBuffer(S[1],Length(S));
542    finally
543      Free;
544    end;
545end;
546
547end.
548
549