Get key names in json file in delphi - json

I have a json string format like this:
{
"LIST":{
"Joseph":{
"item1":0,
"item2":0
},
"John":{
"item1":0,
"item2":0
},
"Fred":{
"item1":0,
"item2":0
}
}
}
I need to get the names, "Joseph", "John", "Fred" and so on... I have a function that will add names to the list, I have no idea what names will be added so I need to get those names.
I can only get the name "LIST" with this code:
js := TlkJSONstreamed.loadfromfile(jsonFile) as TlkJsonObject;
try
ShowMessage( vartostr(js.NameOf[0]) );
finally
s.free;
end;
I'm using lkJSON-1.07 in delphi 7

You can get the names in turn and obtain the next object for each name.
Get the name: js.NameOf[0]
Obtain the object from the name: js[js.NameOf[0]]
The getJSONNames procedure prints all the names contained in a TlkJSONobject object recursively.
procedure getJSONNames(const Ajs: TlkJSONobject);
var
i: Integer;
begin
if Ajs = nil then
Exit
else
for i := 0 to Ajs.Count-1 do begin
WriteLn(Ajs.NameOf[i]);
getJSONNames(TlkJSONobject(Ajs[Ajs.NameOf[i]]));
end;
end;
var
js: TlkJsonObject;
begin
js := TlkJSONstreamed.loadfromfile(jsonFile) as TlkJsonObject;
try
getJSONNames(js);
finally
js.free;
end;
end.

Related

Delphi JsonTextReader fails to read values

I have a very weird situation.
This is the JSON I am trying to parse:
[
{
"username":"xxx",
"email":"xxx#gmail.com",
"custom_title":"xxx title",
"timezone":"Africa\/Cairo",
"message_count":"218",
"alerts_unread":"0",
"like_count":"385",
"friend_count":"0"
}
]
This is my parsing code:
type
TUserData = record
email, timezone: string;
msg, alerts, likes: integer;
end;
procedure TDMRest.parseData(var b: TUserData);
var
jtr: TJsonTextReader;
sr: TStringReader;
begin
//RESTResponseLogin.Content has the above json text
sr := TStringReader.Create(RESTResponseLogin.Content);
try
jtr := TJsonTextReader.Create(sr);
try
while jtr.Read do
begin
if jtr.TokenType = TJsonToken.StartObject then
process(b, jtr);
end;
finally
jtr.Free;
end;
finally
sr.Free;
end;
end;
//here there is a problem
procedure TDMRest.process(var c: TUserData; jtr: TJsonTextReader);
begin
while jtr.Read do
begin
if (jtr.TokenType = TJsonToken.PropertyName) then
begin
if jtr.Value.ToString = 'email' then
begin
jtr.Read;
c.email := jtr.Value.AsString;
end;
if jtr.Value.ToString = 'timezone' then
begin
jtr.Read;
c.timezone := jtr.Value.AsString;
end;
if jtr.Value.ToString = 'message_count' then
begin
jtr.Read;
c.msg := jtr.Value.AsInteger;
end;
if jtr.TokenType = TJsonToken.EndObject then
begin
c.alerts := 0;
c.likes := 0;
exit;
end;
end;
end;
end;
MY PROBLEM: In the process() code, the first 2 if blocks (email and timezone) can read values into my record. But when I add other if blocks (like if jtr.Value.ToString = 'message_count' then), I cannot see the values of my record anymore.
Am I parsing the data properly?
Basically, I need to grab the info from a JSON string and put the data inside a TUserData record.
I have found the above pattern in a book titled "Expert Delphi", and I am pretty sure that the parseData() function is correct. Probably I am missing something in the process.
The TDMRrst is a DataModule; I am giving the function a record, and I'd like the data to be properly parsed.
What is wrong here?
In the JSON you have shown, all of the values are strings, there are no integers. So, when you call jtr.Value.AsInteger for the message_count value, it raises a conversion exception that you are not catching. TValue.AsInteger DOES NOT perform an implicit conversion from string to integer for you.
You will have to use jtr.Value.AsString instead and convert the string to an integer using StrToInt():
if jtr.Value.ToString = 'message_count' then
begin
jtr.Read;
//c.msg := jtr.Value.AsInteger;
c.msg := StrToInt(jtr.Value.AsString);
end;
Do the same for the other "integer" values in the JSON (alerts_unread, like_count, and friend_count).

Create tree structured JSON

I have a list of objects, of type TDepartment which looks like this
TDepartment = class
ID : Integer;
Name : string;
ParentDepartmentID : Integer;
end;
I need to create a TJSONObject, with an array of departments, which all can also have an array of departments. So the depth of this is unknown.
I am at a point right now where it simply doesn't make sense to me, but I would like the resulting JSON to look like this:
"department_id": "5",
"department_name": "100",
"parent_dept_id": "",
"subdepartments": [{
"department_id": "8",
"department_name": "300",
"parent_dept_id": "5",
"subdepartments": [{
"department_id": "1",
"department_name": "310",
"parent_dept_id": "8",
"subdepartments": []
Keep in mind that each level has unknown amount of siblings and children.
I guess i need to write a recursive procedure, but I am unable to visualize it.
First, you probably want your declaration of TDepartmentto match the nested structure you describe:
TDepartment = class
ID : Integer;
Name : string;
ParentDepartmentID : Integer;
SubDepartments: array of TDepartment;
end;
In order to serialize this I would recommend using the SuperObject library rather than the inbuilt JSON classes:
function TDepartment.Serialize: ISuperObject;
var Context: TSuperRttiContext;
begin
Context := TSuperRttiContext.Create;
try
Result := Context.AsJson<TDepartment>(self);
finally
Context.Free;
end;
end;
In the comments, OP mentioned that TDepartment contains a lot more fields, but only the ones in the question should be serialized; also TJSONObject has to be used, and a department does not know about its children. You could do something like that:
function TDepartment.Serialize2(AllDepartments: TList<TDepartment>): TJSONObject;
var Department: TDepartment;
Subdepartments: TJSONArray;
begin
Result := TJSONObject.Create;
Result.AddPair(TJSONPair.Create('department_id', TJSONNumber.Create(ID)));
Result.AddPair(TJSONPair.Create('department_name', Name));
Result.AddPair(TJSONPair.Create('parent_dept_id', TJSONNumber.Create(ParentDepartmentID)));
Subdepartments := TJSonArray.Create;
for Department in AllDepartments do
begin
if (Department.ParentDepartmentID <> ID) then Continue;
Subdepartments.AddElement(Department.Serialize2(AllDepartments));
end;
Result.AddPair(TJSONPair.Create('subdepartments', Subdepartments));
end;
I would create a parallel tree structure leaving the original intact. Your current structure is inverted as to what you need, so you scan through your current objects placing them in the tree. But without knowing the current structure this is difficult give sample code, but assuming all departments exist in some sort of list, (let us say called 'Departments') and the 'root' department has a parent department ID of zero it would go something like this:
unit Unit1;
interface
uses
System.Generics.Collections;
type
TDepartment = class
ID : Integer;
Name : string;
ParentDepartmentID : Integer;
end;
TDepartmentStructure = class
ID : Integer;
Name : string;
ParentDepartmentID : Integer;
SubDepartments: TList< TDepartmentStructure >;
constructor Create( const pBasedOn : TDepartment );
end;
var
Department : TObjectList<TDepartment>;
function CopyStructure( pDepartment : TList<TDepartment> ) : TDepartmentStructure; // returns root
implementation
var
DepartmentStructure : TObjectList<TDepartmentStructure>;
function CopyStructure( pDepartment : TList<TDepartment> ) : TDepartmentStructure;
var
i, j: Integer;
begin
// stage one - copy everything
for i := 0 to pDepartment.Count - 1 do
begin
DepartmentStructure.Add( TDepartmentStructure.Create( pDepartment[ i ] ));
end;
// now go through and build structure
Result := nil;
for i := 0 to DepartmentStructure.Count - 1 do
begin
if DepartmentStructure[ i ].ID = 0 then
begin
// root
Result := DepartmentStructure[ i ];
end
else
begin
for j := 0 to DepartmentStructure.Count - 1 do
begin
if DepartmentStructure[ i ].ParentDepartmentID = DepartmentStructure[ j ].ID then
begin
DepartmentStructure[ j ].SubDepartments.Add( DepartmentStructure[ i ] );
break;
end;
end;
end;
end;
end;
{ TDepartmentStructure }
constructor TDepartmentStructure.Create(const pBasedOn: TDepartment);
begin
inherited Create;
ID := pBasedOn.ID;
Name := pBasedOn.Name;
ParentDepartmentID := pBasedOn.ParentDepartmentID;
SubDepartments:= TObjectList< TDepartmentStructure >.Create( FALSE ); // we do NOT own these objects!
end;
initialization
DepartmentStructure := TObjectList<TDepartmentStructure>.Create( TRUE );
finalization
DepartmentStructure.Free;
end.
Note that this is for illustration purposes only. You would probably not create and destroy the structures where I have. Once you have the structure you can create your JSON records using your current routines no doubt.

How add one object and one pair for a existent .json file?

I have a code that changes a value of a determinated pair in a existent JSON file and works perfectly. Now i need add one object and one pair to this file, using great part this same code. So how do this?
Thank you.
uses
System.Json, ShFolder, System.IOUtils;
...
function GetSpecialFolderPath(folder : integer) : string;
const
SHGFP_TYPE_CURRENT = 0;
var
path: array [0..MAX_PATH] of char;
begin
if SUCCEEDED(SHGetFolderPath(0,folder,0,SHGFP_TYPE_CURRENT,#path[0])) then
Result := path
else
Result := '';
end;
procedure ChangeChromeSetting(const ATarget, Avalue: string);
var
specialfolder: integer;
caminhochrome: String;
JSONObj, Obj: TJSONObject;
JSONPair: TJSONPair;
OldValue: string;
begin
specialFolder := CSIDL_LOCAL_APPDATA;
caminhochrome := GetSpecialFolderPath(specialFolder);
caminhochrome := caminhochrome + '\Google\Chrome\User Data\Local State';
if fileexists(caminhochrome) then
begin
Obj := TJSONObject.Create;
JSONObj := TJSONObject.ParseJSONValue(TFile.ReadAllText(caminhochrome)) as TJSONObject;
if not Assigned(JSONObj) then raise Exception.Create('Cannot read file: ' + caminhochrome);
try
OldValue := JSONObj.GetValue<string>(ATarget);
if not SameText(OldValue, Avalue) then
begin
JSONPair := JSONObj.Get(ATarget);
JSONPair.JsonValue.Free;
JSONPair.JsonValue := TJSONString.Create(Avalue);
///////////////////////////////////////////////////
Obj.AddPair('enabled', TJSONBool.Create(false)); // Trying add pair
JSONObj.AddPair('hardware_acceleration_mode', Obj); // Trying add object
//////////////////////////////////////////////////
TFile.WriteAllText(caminhochrome, JSONObj.ToJSON); // Don't add object and pair
end;
finally
JSONObj.Free;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ChangeChromeSetting('hardware_acceleration_mode_previous', 'false');
end;
This is result that i'm waiting
"hardware_acceleration_mode":{"enabled":false}
Your code is somewhat confusing since you pass in some of the names as arguments, but then hard code others inside the function. Abstracting functionality is good practise but before you can abstract you really need to ensure the code works correctly. I'm going to show code that does not attempt to be abstract. Once you are satisfied it behaves as you need, then feel free to abstract away.
This code does what I believe is your intent:
var
root: TJSONObject;
value: TJSONObject;
prev: string;
begin
root := TJSONObject.ParseJSONValue(TFile.ReadAllText(FileName)) as TJSONObject;
try
prev := root.GetValue<string>('hardware_acceleration_mode_previous');
if not SameText(prev, 'false') then
begin
// remove existing value, if it exists
root.RemovePair('hardware_acceleration_mode').Free;
// create a new object, and initialise it
value := TJSONObject.Create;
value.AddPair('enabled', 'false');
// add the object at the root level
root.AddPair('hardware_acceleration_mode', value);
// save to file
TFile.WriteAllText(FileName, root.ToJSON);
end;
finally
root.Free;
end;
end;
Note that I have ensured that there are no memory leaks. I've used RemovePair to make sure that if there is an existing value named hardware_acceleration_mode it is first removed.

Json Array into ListBox/Memo in Delphi xe7

I'm trying to catch the following JSON array :
[{"name":"Bryan","email":"Bryan#hotmail.com"},
{"name":"Louis","email":"Louis#hotmail.com"},
{"name":"Maria","email":"Maria#hotmail.com"},
{"name":"Test","email":"test#hotmail.com"},
{"name":"Anthony","email":"anthony#hotmail.com"}]
and put it in Memo or ListBox in Delphi :
the code is the following :
procedure TForm1.Button1Click(Sender: TObject);
var jv: TJSONValue;
jo: TJSONObject;
jp: TJSONPair;
ja: TJSONArray;
i: integer;
j: integer;
begin
RESTRequest1.Execute;
jv:=RESTResponse1.JSONValue;
jo:= TJSONObject.ParseJSONValue(jv.ToString) as TJSONObject;
try
for i := 0 to jo.Size - 1 do
begin
jp := jo.Get(i);
if jp.JsonValue is TJSONArray then
begin
ja := jp.JsonValue as TJSONArray;
for j := 0 to ja.Size -1 do
Memo1.Lines.Add(ja.Get(i).ClassName + ': ' + ja.Get(j).ToString);
end
else
Memo1.Lines.Add(jp.ClassName + ': '+ jp.ToString);
end;
finally
jo.Free;
end;
end;
When I click in Button I got the following error message :
Invalid class typecast
during debugging the following line has a problem :
jo:= TJSONObject.ParseJSONValue(jv.ToString) as TJSONObject;
I don't know how to resolve this problem or this mistake ,
Could you please help me ?
Thanks.
This could perfectly well be worked out by reading the code and looking at the JSON. However, I'd like to show you how to go about debugging such a problem in case you cannot work it out by static analysis. When an as cast fails that is always because the object on the left hand side of the as does not derive from the type on the right hand side. The next step then is always to inquire as to what the type of the object on the left hand side is. I've included a short MCVE above as a means to demonstrate.
The output of this program:
{$APPTYPE CONSOLE}
uses
System.JSON;
const
JSON = '[{"name":"Bryan","email":"Bryan#hotmail.com"},' +
' {"name":"Louis","email":"Louis#hotmail.com"},' +
' {"name":"Maria","email":"Maria#hotmail.com"},' +
' {"name":"Test","email":"test#hotmail.com"},' +
' {"name":"Anthony","email":"anthony#hotmail.com"}]';
begin
Writeln(TJSONObject.ParseJSONValue(JSON).ClassName);
end.
is
TJSONArray
Now, TJSONArray does not derive from TJSONObject. Hence your as cast raise a runtime error. If you cast the value returned by ParseJSONValue to TJSONArray that will succeed.
This is to be expected since the root of your JSON is an array and is not an object.
You need to modify your code so that it does not assume that the root level is always an object. You need different behaviour for arrays and objects.
I'm not sure what the problem of TJSONObject is with the string you posted.
For some reason it will parse it if you changed it.
{"Persons":[{"name":"Bryan","email":"Bryan#hotmail.com"},{"name":"Louis","email":"Louis#hotmail.com"},{"name":"Maria","email":"Maria#hotmail.com"},{"name":"Test","email":"test#hotmail.com"},{"name":"Anthony","email":"anthony#hotmail.com"}]}
If I run the code as it is I get the following result
If you don't mind using something different than default Delphi units I would suggest superobject (Link here)
superobject will parse your JSON edited and as posted.
Your code would look like this:
Const
MyJSON = '[{"name":"Bryan","email":"Bryan#hotmail.com"},{"name":"Louis","email":"Louis#hotmail.com"},{"name":"Maria","email":"Maria#hotmail.com"},{"name":"Test","email":"test#hotmail.com"},{"name":"Anthony","email":"anthony#hotmail.com"}]';
procedure ParseJSON;
var
obj: ISuperObject;
Ar: TSuperArray;
I: integer;
begin
obj := SO(MyJSON);
if obj.IsType(stArray) then
begin
Ar := obj.AsArray;
try
for I := 0 to Ar.Length-1 do
L.Add(Ar.O[I].AsString);
finally
Ar.Free;
end;
end
else
L.Add(Obj.AsString);
end;
Result:
For Koul, to get the element names and values.
Like I said not very pretty code but ok.
Ar.O[0].AsObject.GetNames.AsArray.S[0]
To cut it up in pieces a bit.
Ar.O[0] //Get the first element in the array as ISuperObject
.AsObject //Get it as TSuperTableString
.GetNames //Gets all names in the array, in this case "name" and "email"
.AsArray[0]//Get the first name in the names array.
It will result in email (Names are sorted A-Z)
You can do the same for the values by calling GetValues instead of GetNames.
I think the prettiest way to get it will be defining 2x more TSuperArray
procedure PrintNamesAndValues;
Var
Ar, ArNames, ArValues:TSuperArray;
I: Integer;
begin
Ar := SO(<JSON string>).asArray;
ArNames := Ar.O[0].AsObject.GetNames.AsArray;
ArValues := Ar.O[0].AsObject.GetValues.AsArray;
For I := 0 to ArNames.Length-1 do
WriteLn(Format('%s: %s',[ArNames.S[I], ArValues.S[I]]));
end;
Hope it's all clear enough :)

Delphi webservice JSON array

I am really new to Delphi and I am doing an experiment on how to output JSON array through delphi. This maybe sound simple to anyone but I just dont know how. I already created a simple program.
Now, what i want to do is to create a command/request with parameter like:
http://localhost:8001/hello?json={"names":["Jay","Chris","John"]}
that would create a result in the browser like this:
{
result: ["Hello Jay","Hello Chris","Hello John"],
id: "",
time_elapsed: 0
}
Please, i really need help on this. Anybody?
EDIT:
This is the code i just did today but it still doesn't show my desired output:
procedure TPrimeJSONMHelloPeople.ProcessJSONRPCRequest(
var ResultValue: TlkJSONbase; var ResultSuccess: Boolean);
var
jsonPeople:TlkJSONlist;
dmPool:TObject;
dm:TPrimeDataModuleBaseDM;
i:integer;
begin
FjsonObj1 := TlkJSONobject.Create;
jsonPeople := FjsonObj1.CreateListValue('names');
jsonPeople.AddVarString('jay');
jsonPeople.AddVarString('ann');
jsonPeople.AddVarString('john');
inherited;
CheckRequiredParameter('names');
PrimeDataModuleWebService.TDataModuleDMCreateInstanceDefault(dmPool);
try
dm := TPrimeDataModuleDefaultDM(dmPool).GetModule;
try
//this part here will loop and output the name
//if jsonPeople <> nil then
if Params.Field['names'] <> nil then
begin
for i := 0 to FjsonObj1.Field['names'].Count - 1 do
begin
ResultValue := TlkJSONlist.Create
end;
end;
ResultValue := TlkJSONlist.Create;
finally
dm.Release;
end;
finally
dmPool.Free;
end;
FjsonObj1.Free;
ResultSuccess := True;
end;
I don't know what's missing in the code, It only shows:
{
result: [ ],
id: "",
time_elapsed: 0
}
and not :
{
result: ["Hello Jay","Hello Chris","Hello John"],
id: "",
time_elapsed: 0
}
i have just found the right answer. Here's the code:
procedure TSample1.ProcessJSONRPCRequest(
var ResultValue: TlkJSONbase; var ResultSuccess: Boolean);
var
dmPool:TObject;
dm:TPrimeDataModuleBaseDM;
jsonPeople:TlkJSONlist; //used Tlkjsonlist since I want to create an array
i:integer;
begin
inherited;
jsonPeople:=TlkJSONlist.Create; //create jsonPeople as an array
CheckRequiredParameter('names'); //names parameter needed
PrimeDataModuleWebService.TDataModuleDMCreateInstanceDefault(dmPool);
try
dm := TPrimeDataModuleDefaultDM(dmPool).GetModule;
try
if Params.Field['names'] <> nil then //check if the names parameter is empty
begin
ResultValue:=jsonPeople;
for i := 0 to Params.Field['names'].Count - 1 do
begin
jsonPeople.AddVarString('hello ' + Params.Field['names'].Child[i].value);
end;
end;
finally
dm.Release;
end;
finally
dmPool.Free;
end;
ResultSuccess := True;
end;
end.
The request is http://localhost/sample1?json={"names":["john","jay"]}
The output is
{
-
result: [
"hello john"
"hello jay"
]
id: ""
time_elapsed: 0
}
Hope this can help someone who is new in creating web service request using delphi.
First of all, I think your URI shown in your question is already decoded. You should encode the URI parameters in the HTTP protocol.
If you want to create such HTTP-oriented JSON access, take a look at the RESTful approach. It would help you not reinvent the well, and be able to make your server more AJAX ready.
Then you seems to use the third-party lkJSON Delphi library... So you could get directly help from its author or support forum.
From the source code of the library, you should use a TlkJSONlist instance to handle a JSON array, from both URI input and result output.