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
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 ?
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.
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;
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.
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.