Delphi parse JSON array or array
For new readers looking for these answers.
How about this function, or even simpler if you restructure the JSON data?
function getData(JsonString: String; User: String; Field: String): String;
var
JSonValue: TJSonValue;
JsonArray: TJSONArray;
ArrayElement: TJSonValue;
FoundValue: TJSonValue;
begin
Result :='';
// create TJSonObject from string
JsonValue := TJSonObject.ParseJSONValue(JsonString);
// get the array
JsonArray := JsonValue as TJSONArray;
// iterate the array
for ArrayElement in JsonArray do begin
FoundValue := ArrayElement.FindValue(User);
if FoundValue <> nil then begin
Result := ArrayElement.GetValue<string>(User + '.' + Field);
break;
end;
end;
end;
The problem with the sample JSON code above is that it uses the users' names "a" "b" as a JSON-key {key:data} for the users' data. In this way you can't use GetValue("a") in your search for data. Structuring your JSON data differently makes the search process a lot easier. I will later on give an example of this.
A way to handle the given JSON data is by using FindValue so you can check if a field with key "a" or "b" exists.
FoundValue := ArrayElement.FindValue("b");
if FoundValue <> nil then begin
Result := ArrayElement.GetValue<string>("b"+ '.' + "email");
break;
About the 'parsing a JSON array' question: After the data is loaded as a TJSonObject you can change the data into a TJSONArray and iterate the elements.
JsonValue := TJSonObject.ParseJSONValue(JsonString);
JsonArray := JsonValue as TJSONArray;
for ArrayElement in JsonArray do begin
...
A working example code for the given JSON data:
unit JsonArray1;
interface
uses System.JSON;
function getData2(JsonString: String; User: String; Field: String): String;
procedure Test1();
implementation
function getData2(JsonString: String; User: String; Field: String): String;
var
JSonValue: TJSonValue;
JsonArray: TJSONArray;
ArrayElement: TJSonValue;
FoundValue: TJSonValue;
begin
Result :='';
// create TJSonObject from string
JsonValue := TJSonObject.ParseJSONValue(JsonString);
// get the array
JsonArray := JsonValue as TJSONArray;
// iterate the array
for ArrayElement in JsonArray do begin
FoundValue := ArrayElement.FindValue(User);
if FoundValue <> nil then begin
Result := ArrayElement.GetValue<string>(User + '.' + Field);
break;
end;
end;
end;
procedure Test1();
var
DataBase: String;
EmailAddress : String;
Username: String;
begin
DataBase := '[ {"a" : {"username":"aaa","email":"[email protected]"}},' +
'{"b" : {"username":"bbb","email":"[email protected]"}} ]';
EmailAddress := getData2(DataBase, 'b', 'email');
Username := getData2(DataBase, 'a', 'username');
end;
end.
As already mentioned, restructuring the JSON data with proper keys makes the code to find data more simple. Because there is a 1 on 1 relation between the users' data "a":{}, "b":{} it's easy to introduce a 'user' key. Also adding a 'users' key to the array results in all data having keys.
'{"users" : [{ "user":"a", "username":"aaa","email":"[email protected]"},' +
'{ "user":"b", "username":"bbb","email":"[email protected]"}]}';
When you iterate the users, you can now use GetValue with the new "user" key.
if ArrayElement.GetValue<String>('user') = 'b' then begin
Result := ArrayElement.GetValue<String>('email');
By giving the array a key you can now get the array with:
JsonArray := JsonValue.GetValue<TJSONArray>('users');
A working example code for the restructured JSON data:
unit JsonArray2;
interface
uses System.JSON;
function getData2(JsonString: String; User: String; Field: String): String;
procedure Test2();
implementation
function getData2(JsonString: String; User: String; Field: String): String;
var
JSonValue: TJSonValue;
JsonArray: TJSONArray;
ArrayElement: TJSonValue;
FoundValue: TJSonValue;
begin
Result :='';
// create TJSonObject from string
JsonValue := TJSonObject.ParseJSONValue(JsonString);
// get the array
JsonArray := JsonValue.GetValue<TJSONArray>('users');
// iterate the array
for ArrayElement in JsonArray do begin
if ArrayElement.GetValue<String>('user') = User then begin
Result := ArrayElement.GetValue<String>(Field);
break;
end;
end;
end;
procedure Test2();
var
DataBase: String;
EmailAddress : String;
Username: String;
begin
DataBase := '{"users" : [{ "user":"a", "username":"aaa","email":"[email protected]"},' +
'{ "user":"b", "username":"bbb","email":"[email protected]"}]}';
EmailAddress := getData2(DataBase, 'b', 'email');
Username := getData2(DataBase, 'a', 'username');
end;
end.
Your JSON is an array of objects, so FIds
is a TJSONArray
containing TJSONObject
elements. And the a
and b
fields of those objects are themselves objects, not arrays. So FValue is TJSONArray
will always be false while enumerating that array.
Also, (FValue as TJSONArray).Items[0] as TJSONValue).Value = name
is wrong, because a JSON object contains name/value pairs, but you are ignoring the names, and you are trying to enumerate the pairs as if they are elements of an array when they are really not. If you want to enumerate an object's pairs, use the TJSONObject.Count
and TJSONObject.Pairs[]
property. But that is not necessary in this situation since you are looking for a specific pair by its name. TJSONObject
has a Values[]
property for that very purpose.
And TJSONObject.ParseJSONValue(((FValue as TJSONArray).Items[0] as TJSONValue).ToJSON) as TJSONArray
is just plain ridiculous. There is no reason to convert an object back to a JSON string just to parse it again. It has already been parsed once, you don't need to parse it again.
And lastly, FValueInner.GetValue<string>(data)
is wrong, because TJSONValue
does not have a GetValue()
method, let alone one that uses Generics.
Now, with that said, try something more like this instead:
// 'name' can be 'a' or 'b' | 'data' can be 'username' or 'email'
function TTest.getData(const name, data: string): string;
var
FValue, FValueInner: TJSONValue;
begin
Result := '';
for FValue in Fids do
begin
if (FValue is TJSONObject) then
begin
FValueInner := TJSONObject(FValue).Values[name];
if FValueInner <> nil then
begin
if (FValueInner is TJSONObject) then
begin
FValueInner := TJSONObject(FValueInner).Values[data];
if FValueInner <> nil then
Result := FValueInner.Value;
end;
Exit;
end;
end;
end;
end;