Edit JSON array with Inno Setup - json

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;

Related

Delphi DataSnap TJSONInterceptor for Variant don't revert correctly

I have DataSnap Server and I have some server method to Set and Get object with variant fields
here is a n example of object I can access via DataSnap :
type
TOrder = class
private
[JSONReflect(ctObject, rtObject, TSampleVariantInterceptor, nil, true)]
FComment: Variant;
[JSONReflect(ctObject, rtObject, TSampleVariantInterceptor, nil, true)]
FNumber: Variant;
procedure SetComment(const Value: Variant);
procedure SetNumber(const Value: Variant);
public
property Number: Variant read FNumber write SetNumber;
property Comment: Variant read FComment write SetComment;
end;
implementation
{ TOrder }
procedure TOrder.SetComment(const Value: Variant);
begin
FComment := Value;
end;
procedure TOrder.SetNumber(const Value: Variant);
begin
FNumber := Value;
end;
here is my ServerMethod sample code :
TsmOrder = class(TDSServerModule)
public
...
function GetOrder(const AID: Integer): TOrder;
function SetOrder(const AOrder: TOrder): Integer;
end;
here is my unit for where is defined my TSampleVariantInterceptor
unit MarshallingUtils;
interface
uses SysUtils, Classes, DBXJSON, StrUtils, RTTI, DBXJSONReflect, Variants;
type
TSampleVariantInterceptor = class(TJSONInterceptor)
private
public
function ObjectConverter(Data: TObject; Field: String): TObject; override;
procedure ObjectReverter(Data: TObject; Field: String; Arg: TObject); override;
end;
[JSONReflect(true)]
TReflectVariantObject = class
private
FType: TVarType;
FValue: string;
public
constructor Create(ASampleVariant: Variant);
function GetVariant: Variant;
end;
implementation
const
NullVariantString = 'null';
{ TSampleVariantInterceptor }
function TSampleVariantInterceptor.ObjectConverter(Data: TObject;
Field: String): TObject;
var
LRttiContext: TRttiContext;
LVariant: Variant;
begin
LVariant := LRttiContext.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<Variant>;
Result := TReflectVariantObject.Create(LVariant);
end;
procedure TSampleVariantInterceptor.ObjectReverter(Data: TObject; Field: String;
Arg: TObject);
var
LRttiContext: TRttiContext;
LRttiField: TRttiField;
LVariant: Variant;
begin
Assert(Arg is TReflectVariantObject);
LVariant := TReflectVariantObject(Arg).GetVariant;
LRttiField := LRttiContext.GetType(Data.ClassType).GetField(Field);
LRttiField.SetValue(Data, TValue.FromVariant(LVariant));
Arg.Free;
end;
{ TReflectVariantObject }
constructor TReflectVariantObject.Create(ASampleVariant: Variant);
begin
FType := VarType(ASampleVariant);
case FType of
varNull: FValue := NullVariantString;
else
FValue := ASampleVariant; // Convert to string
end;
end;
function TReflectVariantObject.GetVariant: Variant;
var
V: Variant;
begin
if FValue = NullVariantString then
V := Null
else
V := FValue;
VarCast(Result, V, FType);
end;
end.
My variant is well converted in my server : I can't see the null string in fiddler but in my client application my variant appear to Empty instead of Null Variant. Do I make something wrong ?

Marshal a Record to JSON and back [duplicate]

How can an array of record be stored in JSON via SuperObject library. For example..
type
TData = record
str: string;
int: Integer;
bool: Boolean;
flt: Double;
end;
var
DataArray: Array[0..100] of TData;
Just use the superobject Marshalling TSuperRTTIContext
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
superobject,
System.SysUtils;
type
TData = record
str : string;
int : Integer;
bool : Boolean;
flt : Double;
end;
TDataArray = Array [0 .. 100] of TData;
procedure Test;
var
DataArray : TDataArray;
so : ISuperObject;
ctx : TSuperRttiContext;
begin
ctx := TSuperRttiContext.Create;
try
so := ctx.AsJson<TDataArray>( DataArray );
finally
ctx.Free;
end;
Writeln( so.AsJson );
end;
begin
try
Test;
except
on E : Exception do
Writeln( E.ClassName, ': ', E.Message );
end;
ReadLn;
end.
Make it a string first.
Your array:
//Array[0] := 'Apple';
//Array[1] := 'Orange';
//Array[2] := 'Banana';
myArrayAsStr := '"MyArray": [{ "1": "' + Array[0] +'", "2": "' + Array[1] +'"}';
Then you can just make it into JSON with SO(myArrayAsStr)
You can always generate your array as string in a different procedure but I think thats the way to do it.
Ill keep checking if there is an easier way ;)
EDIT:
SuperObject also has the following function:
function SA(const Args: array of const): ISuperObject; overload;
You will be able to convert that to a string again and add it in the total JSON string.

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.

How to parse a JSON string in Inno Setup?

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.