How to parse a JSON string in Inno Setup? - json

I have the following JSON:
{
"Info": {
"User": 2,
"String": "foo"
}
}
Unfortunately TLama's Inno JSON Config library doesn't work with JSON strings but only with json files.
I tried to use JSON string instead of path to json file, but it didn't work.
if JSONQueryInteger('{"Info":{"User":2,"String":"foo"}}', 'Info', 'User', 0, IntValue) then
MsgBox('User=' + IntToStr(IntValue), mbInformation, MB_OK);
I know I could save my JSON to a file and then parse it but it seems kind of messy.
How to parse a JSON string in Inno Setup?

You can use JsonParser library instead. It can parse JSON strings.
It's not as easy to use as JSONConfig.dll – but that's the reason why it is more flexible. Also it's a native Pascal Script code. So, it not only saves you from a temporary .json file, but also from a temporary .dll.
The code can be like:
[Code]
#include "JsonParser.pas"
function GetJsonRoot(Output: TJsonParserOutput): TJsonObject;
begin
Result := Output.Objects[0];
end;
function FindJsonValue(
Output: TJsonParserOutput; Parent: TJsonObject; Key: TJsonString;
var Value: TJsonValue): Boolean;
var
I: Integer;
begin
for I := 0 to Length(Parent) - 1 do
begin
if Parent[I].Key = Key then
begin
Value := Parent[I].Value;
Result := True;
Exit;
end;
end;
Result := False;
end;
function FindJsonObject(
Output: TJsonParserOutput; Parent: TJsonObject; Key: TJsonString;
var Object: TJsonObject): Boolean;
var
JsonValue: TJsonValue;
begin
Result :=
FindJsonValue(Output, Parent, Key, JsonValue) and
(JsonValue.Kind = JVKObject);
if Result then
begin
Object := Output.Objects[JsonValue.Index];
end;
end;
function FindJsonNumber(
Output: TJsonParserOutput; Parent: TJsonObject; Key: TJsonString;
var Number: TJsonNumber): Boolean;
var
JsonValue: TJsonValue;
begin
Result :=
FindJsonValue(Output, Parent, Key, JsonValue) and
(JsonValue.Kind = JVKNumber);
if Result then
begin
Number := Output.Numbers[JsonValue.Index];
end;
end;
function FindJsonString(
Output: TJsonParserOutput; Parent: TJsonObject; Key: TJsonString;
var Str: TJsonString): Boolean;
var
JsonValue: TJsonValue;
begin
Result :=
FindJsonValue(Output, Parent, Key, JsonValue) and
(JsonValue.Kind = JVKString);
if Result then
begin
Str := Output.Strings[JsonValue.Index];
end;
end;
function ParseJsonAndLogErrors(
var JsonParser: TJsonParser; const Source: WideString): Boolean;
var
I: Integer;
begin
ParseJson(JsonParser, Source);
Result := (Length(JsonParser.Output.Errors) = 0);
if not Result then
begin
Log('Error parsing JSON');
for I := 0 to Length(JsonParser.Output.Errors) - 1 do
begin
Log(JsonParser.Output.Errors[I]);
end;
end;
end;
procedure ParseJsonString;
var
Json: string;
JsonParser: TJsonParser;
I: Integer;
JsonRoot, InfoObject: TJsonObject;
UserNumber: TJsonNumber; { = Double }
UserString: TJsonString; { = WideString = string }
begin
Json := '{"Info":{"User":2,"String":"abc"}}';
if ParseJsonAndLogErrors(JsonParser, Json) then
begin
JsonRoot := GetJsonRoot(JsonParser.Output);
if FindJsonObject(JsonParser.Output, JsonRoot, 'Info', InfoObject) and
FindJsonNumber(JsonParser.Output, InfoObject, 'User', UserNumber) and
FindJsonString(JsonParser.Output, InfoObject, 'String', UserString) then
begin
Log(Format('Info:User:%d', [Round(UserNumber)]));
Log(Format('Info:String:%s', [UserString]));
end;
end;
ClearJsonParser(JsonParser);
end;
Another option is to fork the Inno JSON Config library and add support for parsing strings.

Related

Edit JSON array with Inno Setup

I want to edit a JSON file with Inno Setup for entering the installation path of my programme.
I have also found the post: How to parse a JSON string in Inno Setup? however, I can't cope with it because it talks about Info, User & String, but I only have Info & User.
This is what the entry that needs to be edited looks like:
"game_dirs": [
"Installation path"
]
This is my code:
[Code]
function JSONQueryString(FileName, Section, Key, Default: WideString;
var Value: WideString; var ValueLength: Integer): Boolean;
external 'JSONQueryString#files:jsonconfig.dll stdcall';
function JSONQueryBoolean(FileName, Section, Key: WideString;
Default: Boolean; var Value: Boolean): Boolean;
external 'JSONQueryBoolean#files:jsonconfig.dll stdcall';
function JSONQueryInteger(FileName, Section, Key: WideString;
Default: Int64; var Value: Int64): Boolean;
external 'JSONQueryInteger#files:jsonconfig.dll stdcall';
function JSONWriteString(FileName, Section, Key,
Value: WideString): Boolean;
external 'JSONWriteString#files:jsonconfig.dll stdcall';
function JSONWriteBoolean(FileName, Section, Key: WideString;
Value: Boolean): Boolean;
external 'JSONWriteBoolean#files:jsonconfig.dll stdcall';
function JSONWriteInteger(FileName, Section, Key: WideString;
Value: Int64): Boolean;
external 'JSONWriteInteger#files:jsonconfig.dll stdcall';
function BoolToStr(Value: Boolean): string;
begin
Result := 'True';
if not Value then
Result := 'False';
end;
procedure InitializeWizard;
var
FileName: WideString;
IntValue: Int64;
StrValue: WideString;
StrLength: Integer;
BoolValue: Boolean;
begin
{ set the source JSON config file path }
FileName := '{app}\\PATH\\Config.json';
{ allocate string buffer to enough length }
SetLength(StrValue, 16);
{ set the buffer length value }
StrLength := Length(StrValue);
{ query string value }
if JSONQueryString(FileName, 'game_dirs', '{app}\TEST', 'Default', StrValue,
StrLength)
then
MsgBox('Section_1:Key_1=' + StrValue, mbInformation, MB_OK);
{ query integer value }
if JSONQueryInteger(FileName, 'Section_1', 'Key_2', 0, IntValue) then
MsgBox('Section_1:Key_2=' + IntToStr(IntValue), mbInformation, MB_OK);
{ query boolean value }
end;
The following error message appears during installation:
Runtime error (at 46:224): Could not call proc.
Your game_dirs element is a JSON array. I do not think that JSONConfig.dll supports arrays.
You can use JsonParser library instead. With use of additional functions from my answer to How to parse a JSON string in Inno Setup?, the code can be like:
function FindJsonArray(
Output: TJsonParserOutput; Parent: TJsonObject; Key: TJsonString;
var Arr: TJsonArray): Boolean;
var
JsonValue: TJsonValue;
begin
Result :=
FindJsonValue(Output, Parent, Key, JsonValue) and
(JsonValue.Kind = JVKArray);
if Result then
begin
Arr := Output.Arrays[JsonValue.Index];
end;
end;
procedure EditGameDirs(FileName: string; GameDir: string);
var
JsonLines: TStringList;
JsonParser: TJsonParser;
JsonRoot: TJsonObject;
GameDirs: TJsonArray;
begin
JsonLines := TStringList.Create;
JsonLines.LoadFromFile(FileName);
if ParseJsonAndLogErrors(JsonParser, JsonLines.Text) then
begin
JsonRoot := GetJsonRoot(JsonParser.Output);
if not FindJsonArray(JsonParser.Output, JsonRoot, 'game_dirs', GameDirs) then
begin
Log('Cannot find game_dirs');
end
else
if (GetArrayLength(GameDirs) <> 1) or
(GameDirs[0].Kind <> JVKString) then
begin
Log('game_dirs does not have one string element');
end
else
begin
Log(Format('Previous game_dirs was [%s]', [
JsonParser.Output.Strings[GameDirs[0].Index]]));
Log(Format('New game_dirs is [%s]', [GameDir]));
JsonParser.Output.Strings[GameDirs[0].Index] := GameDir;
JsonLines.Clear;
PrintJsonParserOutput(JsonParser.Output, JsonLines);
JsonLines.SaveToFile(FileName + '2');
end;
end;
end;

JSON object- how to iterate through all properties without knowing their names?

I have a fairly simple JSON:
[
{
"hero-img": "girl-ipad.png",
"main-story-headline": "How to Stay Mentally Healthy During a Pandemic",
"main-story-paragraph": "lorem ipsum",
"main-story-button-text": "Read More"
},
{
"hero-img": "painter-woman.png",
"main-story-headline": "How to Stay Mentally Healthy During a Pandemic",
"main-story-paragraph": "lorem ipsum",
"main-story-button-text": "Explore More"
},
{
"hero-img": "old-man.png",
"main-story-headline": "How to Stay Mentally Healthy During a Pandemic",
"main-story-paragraph": "lorem ipsum",
"main-story-button-text": "Get Yours Now"
} ]
By using Delphi 10.3 Rio, I want to iterate through all properties no matter how they are named. I've started this way:
program JuTemplates;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.JSON, System.IOUtils, System.Types;
var
root, OutputDir, TemplatesDir, JsonDir, jsonText: string;
JsonFiles: TStringDynArray;
i, j: Integer;
JSONValue: TJSONObject;
JSONValues: TJSONArray;
begin
try
try
root := ExtractFilePath(ParamStr(0));
OutputDir := root + 'OutputDir\';
TemplatesDir := root + 'TemplatesDir\';
JsonDir := root + 'JsonDir\';
writeln('Processing: ' + JsonDir);
JsonFiles := TDirectory.GetFiles(JsonDir);
for i := 0 to High(JsonFiles) do
begin
jsonText := TFILE.ReadAllText(JsonFiles[i]);
JSONValues := TJSONObject.ParseJSONValue(jsonText) as TJSONArray;
for j := 0 to JSONValues.Count - 1 do
begin
JSONValue := JSONValues.Items[i] as TJSONObject;
// here I should iterate through that JSONValue object
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
finally
writeln ('Press any key to continue');
readln;
end;
end.
To enumerate a JSONObject, you can use an enumerator like below.
Use for..in loop (implicit enumerator)
procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONPair : TJSONPair;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
for JSONPair in JSONObject do
ShowMessage(Format('1) Key=%s Value=%s',
[JSONPair.JsonString.ToString,
JSONPair.JsonValue.ToString]));
finally
JSONObject.Free;
end;
end;
Use a classic for loop:
procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONPair : TJSONPair;
I : Integer;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
for I := 0 to JSONObject.Count - 1 do begin
ShowMessage(Format('Key=%s Value=%s',
[JSONObject.Pairs[I].JsonString.ToString,
JSONObject.Pairs[I].JsonValue.ToString]));
end;
finally
JSONObject.Free;
end;
end;
Use an explicit enumerator and a while loop:
procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONEnumerator : TJSONObject.TEnumerator;
JSONPair : TJSONPair;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
JSONEnumerator := JSONObject.GetEnumerator;
try
while JSONEnumerator.MoveNext do begin
JSONPair := JSONEnumerator.Current;
ShowMessage(Format('Key=%s Value=%s',
[JSONPair.JsonString.ToString,
JSONPair.JsonValue.ToString]));
end;
finally
JSONEnumerator.Free;
end;
finally
JSONObject.Free;
end;
end;
Note that you may need to check if JSONPair.JsonValue has childs and enumerate those childs with another enumerator, recursively.

Retrieving JSON data from URL in Delphi XE7

I'm trying to get JSON data from an URL. The site I'm trying to connect is:
http://www.bitven.com/assets/js/rates.js
It contains the following JSON string:
{
"USD_TO_BSF_RATE": 112268.29,
"BTC_TO_USD_RATE": 13870.9,
"ETH_TO_USD_RATE": 752.222,
"BCH_TO_USD_RATE": 2960.81,
"LTC_TO_USD_RATE": 272.476,
"XRP_TO_USD_RATE": 1.01954,
"ETC_TO_USD_RATE": 31.1101,
"DASH_TO_USD_RATE": 1178.0,
"ZEC_TO_USD_RATE": 561.377,
"XMR_TO_USD_RATE": 354.709
}
I need to get the value of USD_TO_BSF_RATE, which is updated every 5 minutes in the site I mentioned. My code looks like:
uses
... System.JSON, IdHTTP;
function GetUrlContent(s: string): string;
var
IdHTTP1: TIdHTTP;
begin
IdHTTP1.Create;
GetUrlContent:=IdHTTP1.Get(s);
IdHTTP1.Destroy;
end;
procedure DolarUpdate;
var
json: string;
obj: TJSONObject;
url: string;
begin
try
json:=GetUrlContent('http://www.bitven.com/assets/js/rates.js');
try
obj := TJSONObject.ParseJSONValue(json) as TJSONObject;
TabbedForm.Edit2.Text := obj.Values['USD_TO_BSF_RATE'].Value;
finally
obj.Free;
end;
except
on E : Exception do
begin
ShowMessage('Error'+sLineBreak+E.ClassName+sLineBreak +E.Message);
end;
end;
end;
My app doesn't function correctly, nor return any messages. It only crashes.
What am I doing wrong?
Your GetUrlContent() function is not coded correctly. It needs to look like this instead:
function GetUrlContent(s: string): string;
var
IdHTTP1: TIdHTTP;
begin
IdHTTP1 := TIdHTTP.Create;
try
Result := IdHTTP1.Get(s);
finally
IdHTTP1.Free;
end;
end;
And your DolarUpdate() procedure should look more like this instead:
procedure DolarUpdate;
var
json: string;
obj: TJSONObject;
url: string;
begin
try
json := GetUrlContent('http://www.bitven.com/assets/js/rates.js');
obj := TJSONObject.ParseJSONValue(json) as TJSONObject;
if obj = nil then raise Exception.Create('Error parsing JSON');
try
TabbedForm.Edit2.Text := obj.Values['USD_TO_BSF_RATE'].Value;
finally
obj.Free;
end;
except
on E : Exception do
begin
ShowMessage('Error' + sLineBreak + E.ClassName + sLineBreak + E.Message);
end;
end;
end;

Inno Setup: Change value in JSON array

I would like to create an installer with the help of Inno Setup. In order to make the program work on every computer, I need to change the tool directory in a .json-file. Here is an excerpt from this file:
{
"commandScriptLinux" : "",
"copyToolBehavior" : "once",
"deleteWorkingDirectoriesAfterWorkflowExecution" : true,
"deleteWorkingDirectoriesKeepOnErrorOnce" : true,
"deleteWorkingDirectoriesNever" : true,
"documentationFilePath" : "",
"enableCommandScriptWindows" : true,
"imitationScript" : "",
"imitationToolOutputFilename" : "",
"launchSettings" :
[
{
"limitInstallationInstancesNumber" : "1",
"limitInstallationInstances" : "false",
"toolDirectory" : "%Selected Setup Folder%",
"version" : "1.0"
}
],
}
I hoped to solve this by using the inno-json-config library. Unfortunately, after executing the code, the lines are reversed (last line comes first now) and the changes weren't made.
[Setup]
AppName=Change_Config
AppVersion=1.0
DefaultDirName={userdocs}\Change_Config
[Files]
Source: "JSONConfig.dll"; Flags: dontcopy
[Code]
function JSONQueryString(FileName, Section, Key, Default: WideString;
var Value: WideString; var ValueLength: Integer): Boolean;
external 'JSONQueryString#files:jsonconfig.dll stdcall';
function JSONWriteString(FileName, Section, Key,
Value: WideString): Boolean;
external 'JSONWriteString#files:jsonconfig.dll stdcall';
procedure InitializeWizard;
var
FileName: WideString;
IntValue: Int64;
StrValue: WideString;
StrLength: Integer;
BoolValue: Boolean;
begin
FileName := 'c:\configuration.json';
SetLength(StrValue, 16);
StrLength := Length(StrValue);
if JSONQueryString(
FileName, 'launchSettings', 'toolDirectory', 'Default', StrValue, StrLength) then
MsgBox('Section_1:Key_1=' + StrValue, mbInformation, MB_OK);
if not JSONWriteString(FileName, 'launchSettings', 'toolDirectory', 'Test') then
MsgBox('JSONWriteString Section_1:Key_1 failed!', mbError, MB_OK);
end;
Thank you very much for your support!
Regards,
Alex
The launchSettings is an array. I believe that the inno-json-config library does not support arrays.
You can use JsonParser library instead.
[Code]
#include "JsonParser.pas"
function FindJsonValue(
Output: TJsonParserOutput; Parent: TJsonObject; Key: TJsonString;
var Value: TJsonValue): Boolean;
var
I: Integer;
begin
for I := 0 to Length(Parent) - 1 do
begin
if Parent[I].Key = Key then
begin
Value := Parent[I].Value;
Result := True;
Exit;
end;
end;
Result := False;
end;
function FindJsonArray(
Output: TJsonParserOutput; Parent: TJsonObject; Key: TJsonString;
var Arr: TJsonArray): Boolean;
var
JsonValue: TJsonValue;
begin
Result :=
FindJsonValue(Output, Parent, Key, JsonValue) and
(JsonValue.Kind = JVKArray);
if Result then
begin
Arr := Output.Arrays[JsonValue.Index];
end;
end;
{ ... }
var
JsonLines: TStringList;
JsonParser: TJsonParser;
LaunchSettingsArray: TJsonArray;
ToolDirectoryValue: TJsonValue;
I: Integer;
begin
{ ... }
JsonLines := TStringList.Create;
JsonLines.LoadFromFile(FileName);
ParseJson(JsonParser, JsonLines.Text);
if Length(JsonParser.Output.Errors) > 0 then
begin
Log('Error parsing JSON');
for I := 0 to Length(JsonParser.Output.Errors) - 1 do
begin
Log(JsonParser.Output.Errors[I]);
end;
end
else
begin
if FindJsonArray(
JsonParser.Output, JsonParser.Output.Objects[0],
'launchSettings', LaunchSettingsArray) and
(GetArrayLength(LaunchSettingsArray) >= 0) and
(LaunchSettingsArray[0].Kind = JVKObject) and
FindJsonValue(
JsonParser.Output,
JsonParser.Output.Objects[LaunchSettingsArray[0].Index], 'toolDirectory',
ToolDirectoryValue) and
(ToolDirectoryValue.Kind = JVKString) then
begin
Log(Format(
'launchSettings[0]:toolDirectory:%s', [
JsonParser.Output.Strings[ToolDirectoryValue.Index]]));
JsonParser.Output.Strings[ToolDirectoryValue.Index] := 'Test';
JsonLines.Clear;
PrintJsonParserOutput(JsonParser.Output, JsonLines);
JsonLines.SaveToFile(FileName);
end;
end;
ClearJsonParser(JsonParser);
JsonLines.Free;
end;
It still won't preserve the order (but that should not matter).
Though in your case, you do not need to parse the JSON. You can simply replace the %Selected Setup Folder% with the desired value.
See Replace placeholder in an installed text file with input entered by user.

Delphi XE5 JSON nested object

I am trying to generate JSON, data source is a DB FireBird.
I have a schema to generate path like this:
schema:= TDictionary<string, string>.Create;
schema.Add('DBTableName1', 'nest1.valueKeyName1');
schema.Add('DBTableName2', 'nest1.valueKeyName2');
schema.Add('DBTableName3', 'nest2.valueKeyName1');
schema.Add('DBTableName4', 'nest3.valueKeyName1');
schema.Add('DBTableName5', 'nest3.valueKeyName2');
schema.Add('DBTableName6', 'nest4.valueKeyName1');
How to create function for generate path to make nested objects?
{
"nest1": {
valueKeyName1: DBTableValue1,
valueKeyName2: DBTableValue2,
},
"nest2": {
valueKeyName1: DBTableValue3
},
"nest5":{
"nest6": {
"key1": "value1",
"key2": "value2",
},
"nest7": {}
}
In JavaScript I can do something like:
if (object.hasOwnProperty['key'] == false) object['key'] = {};
object = object['key'];
But in Delphi I have problem, and do not know, how to go deeper:
function TKlient.wprowadzWartoscDoJSON(wartosc: string; JSON: TJSONObject; sciezka: TStringList): TJSONObject;
var
i: integer;
obiekt: TJSONObject;
para: TJSONPair;
zagniezdzen: integer;
begin
zagniezdzen := sciezka.Count - 2;
obiekt := JSON;
para:= obiekt.Get(sciezka[i]);
for i := 1 to zagniezdzen do
begin
if obiekt.Get(sciezka[i]) = nil then obiekt.AddPair(sciezka[i], TJSONObject.Create)
else obiekt := obiekt.Get(sciezka[i]);
end;
obiekt.AddPair(sciezka[sciezka.Count - 1], wartosc);
result := obiekt;
end;
Here I've used a TDictionary<string, TJSONObject> to keep track of the object paths.
var
schema: TDictionary<string, string>;
pathSchema: TDictionary<string{path to every object}, TJSONObject>;
pair: TPair<string, string>;
values: TStringDynArray;
jsonObj,
jsonChildObj,
jsonParentObj: TJSONObject;
path: string;
i: Integer;
begin
schema := TDictionary<string, string>.Create;
try
schema.Add('DBTableName1', 'nest1.valueKeyName1');
schema.Add('DBTableName2', 'nest1.valueKeyName2');
schema.Add('DBTableName3', 'nest2.valueKeyName1');
schema.Add('DBTableName4', 'nest3.valueKeyName1');
schema.Add('DBTableName5', 'nest3.valueKeyName2');
schema.Add('DBTableName6', 'nest4.valueKeyName1');
schema.Add('value1', 'nest5.nest6.key1');
schema.Add('value2', 'nest5.nest6.key2');
pathSchema := TDictionary<string, TJSONObject>.Create;
try
jsonObj := TJSONObject.Create;
try
for pair in schema do begin
values := SplitString(pair.Value, '.');
path := '';
jsonParentObj := jsonObj;
for i := Low(values) to High(values)-1 do begin
if i > 0 then
path := path + '.';
path := path + values[i];
if pathSchema.ContainsKey(path) then
jsonChildObj := pathSchema[path]
else begin
jsonChildObj := TJSONObject.Create;
jsonParentObj.AddPair(TJSONPair.Create(values[i], jsonChildObj));
pathSchema.Add(path, jsonChildObj);
end;
jsonParentObj := jsonChildObj;
end;
jsonChildObj.AddPair(TJSONPair.Create(values[High(values)], pair.Key));
end;
WriteLn(jsonObj.ToString);
finally
jsonObj.Free;
end;
finally
pathSchema.Free;
end;
finally
schema.Free;
end;
ReadLn;
end.
The above prints the following:
{
"nest4":{
"valueKeyName1":"DBTableName6"
},
"nest2":{
"valueKeyName1":"DBTableName3"
},
"nest5":{
"nest6":{
"key1":"value1",
"key2":"value2"
}
},
"nest1":{
"valueKeyName1":"DBTableName1",
"valueKeyName2":"DBTableName2"
},
"nest3":{
"valueKeyName1":"DBTableName4",
"valueKeyName2":"DBTableName5"
}
}