unit _JSONConfig;

interface

uses
  Classes, SysUtils, JsonDataObjects;

type
  TJSONConfig = class(TObject)
  private
    FJSONDoc: TJsonObject;
    FFileName: string;
    FFileModified: Boolean;
    FAutoSave: Boolean;
    function GetRawJSONContent: string;
  public
    constructor Create(const AFileName: string; AThrowExceptionOnCorruptFile: Boolean = True);
    destructor Destroy; override;
    procedure Save(SaveWithFormatting: Boolean = True);
    procedure ClearDocument;
    procedure WriteString(const ASection: string; const AKey: string; const AValue: string);
    procedure WriteInteger(const ASection: string; const AKey: string; const AValue: Integer);
    procedure WriteBool(const ASection: string; const AKey: string; const AValue: Boolean);
    procedure WriteDateTime(const ASection: string; const AKey: string; const AValue: TDateTime);
    procedure WriteDate(const ASection: string; const AKey: string; const AValue: TDateTime);
    procedure WriteTime(const ASection: string; const AKey: string; const AValue: TDateTime);
    procedure WriteFloat(const ASection: string; const AKey: string; const AValue: Double);
    procedure DeleteSection(const ASection: string);
    procedure DeleteKey(const ASection: string; const AKey: string);
    procedure ReadSectionKeys(const ASection: string; AStrings: TStrings);
    procedure ReadSectionValues(const ASection: string; AStrings: TStrings);
    procedure ReadSections(AStrings: TStrings);
    procedure CopySection(const AFromSection: string; const AToSection: string);
    function SectionExists(ASection: string): Boolean;
    function KeyExists(const ASection: string; const AKey: string): Boolean;
    function ReadString(const ASection: string; const AKey: string; ADefault: string = ''): string;
    function ReadInteger(const ASection: string; const AKey: string; ADefault: Integer = 0): Integer;
    function ReadBool(const ASection: string; const AKey: string; ADefault: Boolean = False): Boolean;
    function ReadDateTime(const ASection: string; const AKey: string; ADefault: TDateTime = 0): TDateTime;
    function ReadDate(const ASection: string; const AKey: string; ADefault: TDateTime = 0): TDateTime;
    function ReadTime(const ASection: string; const AKey: string; ADefault: TDateTime = 0): TDateTime;
    function ReadFloat(const ASection: string; const AKey: string; ADefault: Double = 0): Double;

    property FileName: string read FFileName;
    property FileModified: Boolean read FFileModified;
    property RawJSONContent: string read GetRawJSONContent;
    property AutoSave: Boolean read FAutoSave write FAutoSave;
  end;

implementation

procedure TJSONConfig.ClearDocument;
begin
  FJSONDoc.Clear;
  FFileModified := True;
end;

procedure TJSONConfig.CopySection(const AFromSection, AToSection: string);
begin
  if SectionExists(AFromSection) then
  begin
    FJSONDoc.O[AToSection].Assign(FJSONDoc.O[AFromSection]);
    FFileModified := True;
  end;
end;

constructor TJSONConfig.Create(const AFileName: string; AThrowExceptionOnCorruptFile: Boolean = True);
begin
  FFileModified := False;
  FFileName := AFileName;
  FAutoSave := True;

  FJSONDoc := TJsonObject.Create;

  if FileExists(AFileName) then
  begin
    try
      FJSONDoc.LoadFromFile(FFileName);
    except
      if AThrowExceptionOnCorruptFile then
        raise;
    end;
  end;
end;

procedure TJSONConfig.DeleteKey(const ASection, AKey: string);
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
    begin
      FJSONDoc.O[ASection].Remove(AKey);
      FFileModified := True;
    end;
  end;
end;

procedure TJSONConfig.DeleteSection(const ASection: string);
begin
  if FJSONDoc.IndexOf(ASection) > -1 then
  begin
    FJSONDoc.Remove(ASection);
    FFileModified := True;
  end;
end;

destructor TJSONConfig.Destroy;
begin
  if FAutoSave then
  begin
    Save;
  end;

  FJSONDoc.Free;
  inherited;
end;

function TJSONConfig.GetRawJSONContent: string;
begin
  Result := FJSONDoc.ToJSON(False);
end;

function TJSONConfig.KeyExists(const ASection, AKey: string): Boolean;
begin
  if SectionExists(ASection) then
  begin
    Result := FJSONDoc.O[ASection].IndexOf(AKey) > -1;
  end
  else
  begin
    Result := False;
  end;
end;

function TJSONConfig.ReadBool(const ASection, AKey: string; ADefault: Boolean = False): Boolean;
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
      Result := FJSONDoc.O[ASection].B[AKey]
    else
      Result := ADefault;
  end
  else
  begin
    Result := ADefault;
  end;
end;

function TJSONConfig.ReadDate(const ASection, AKey: string; ADefault: TDateTime = 0): TDateTime;
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
      Result := StrToDateDef(ReadString(ASection, AKey), ADefault)
    else
      Result := ADefault;
  end
  else
  begin
    Result := ADefault;
  end;
end;

function TJSONConfig.ReadDateTime(const ASection, AKey: string; ADefault: TDateTime = 0): TDateTime;
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
      Result := StrToDateTimeDef(ReadString(ASection, AKey), ADefault)
    else
      Result := ADefault;
  end
  else
  begin
    Result := ADefault;
  end;
end;

function TJSONConfig.ReadFloat(const ASection, AKey: string; ADefault: Double = 0): Double;
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
      Result := FJSONDoc.O[ASection].F[AKey]
    else
      Result := ADefault;
  end
  else
  begin
    Result := ADefault;
  end;
end;

function TJSONConfig.ReadInteger(const ASection, AKey: string;
  ADefault: Integer): Integer;
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
      Result := FJSONDoc.O[ASection].I[AKey]
    else
      Result := ADefault;
  end
  else
  begin
    Result := ADefault;
  end;
end;

procedure TJSONConfig.ReadSectionKeys(const ASection: string;
  AStrings: TStrings);
var
  i: Integer;
begin
  AStrings.Clear;
  if SectionExists(ASection) then
  begin
    for i := 0 to FJSONDoc.O[ASection].Count - 1 do
    begin
      AStrings.Add(FJSONDoc.O[ASection].Names[i]);
    end;
  end;
end;

procedure TJSONConfig.ReadSections(AStrings: TStrings);
var
  i: Integer;
begin
  AStrings.Clear;
  for i := 0 to FJSONDoc.Count - 1 do
  begin
    if FJSONDoc.Items[i].Typ = jdtObject then
    begin
      AStrings.Add(FJSONDoc.Names[i]);
    end;
  end;
end;

procedure TJSONConfig.ReadSectionValues(const ASection: string;
  AStrings: TStrings);
var
  i: Integer;
begin
  AStrings.Clear;
  if SectionExists(ASection) then
  begin
    for i := 0 to FJSONDoc.O[ASection].Count - 1 do
    begin
      AStrings.Add(FJSONDoc.O[ASection].Values[FJSONDoc.O[ASection].Names[i]]);
    end;
  end;
end;

function TJSONConfig.ReadString(const ASection, AKey: string; ADefault: string = ''): string;
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
      Result := FJSONDoc.O[ASection].S[AKey]
    else
      Result := ADefault;
  end
  else
  begin
    Result := ADefault;
  end;
end;

function TJSONConfig.ReadTime(const ASection, AKey: string; ADefault: TDateTime = 0): TDateTime;
begin
  if SectionExists(ASection) then
  begin
    if FJSONDoc.O[ASection].IndexOf(AKey) > -1 then
      Result := StrToTimeDef(ReadString(ASection, AKey), ADefault)
    else
      Result := ADefault;
  end
  else
  begin
    Result := ADefault;
  end;
end;

procedure TJSONConfig.Save(SaveWithFormatting: Boolean = True);
begin
  if FFileModified then
  begin
    FJSONDoc.SaveToFile(FFileName, not SaveWithFormatting);
    FFileModified := False;
  end;
end;

function TJSONConfig.SectionExists(ASection: string): Boolean;
begin
  Result := FJSONDoc.IndexOf(ASection) > -1;
end;

procedure TJSONConfig.WriteBool(const ASection, AKey: string;
  const AValue: Boolean);
begin
  if (not FJSONDoc.O[ASection].Contains(AKey)) or (FJSONDoc.O[ASection].B[AKey] <> AValue) then
  begin
    FJSONDoc.O[ASection].B[AKey] := AValue;
    FFileModified := True;
  end;
end;

procedure TJSONConfig.WriteDate(const ASection, AKey: string;
  const AValue: TDateTime);
begin
  WriteString(ASection, AKey, DateToStr(AValue));
end;

procedure TJSONConfig.WriteDateTime(const ASection, AKey: string;
  const AValue: TDateTime);
begin
  WriteString(ASection, AKey, DateTimeToStr(AValue));
end;

procedure TJSONConfig.WriteFloat(const ASection, AKey: string;
  const AValue: Double);
begin
  if (not FJSONDoc.O[ASection].Contains(AKey)) or (FJSONDoc.O[ASection].F[AKey] <> AValue) then
  begin
    FJSONDoc.O[ASection].F[AKey] := AValue;
    FFileModified := True;
  end;
end;

procedure TJSONConfig.WriteInteger(const ASection, AKey: string;
  const AValue: Integer);
begin
  if (not FJSONDoc.O[ASection].Contains(AKey)) or (FJSONDoc.O[ASection].I[AKey] <> AValue) then
  begin
    FJSONDoc.O[ASection].I[AKey] := AValue;
    FFileModified := True;
  end;
end;

procedure TJSONConfig.WriteString(const ASection, AKey, AValue: string);
begin
  if (not FJSONDoc.O[ASection].Contains(AKey)) or (FJSONDoc.O[ASection].S[AKey] <> AValue) then
  begin
    FJSONDoc.O[ASection].S[AKey] := AValue;
    FFileModified := True;
  end;
end;

procedure TJSONConfig.WriteTime(const ASection, AKey: string;
  const AValue: TDateTime);
begin
  WriteString(ASection, AKey, TimeToStr(AValue));
end;

end.
