I want to parse the JSON and extract all ID values and assign, show them in a combo box.
I am using ULKJson library and Delphi 7. Here is my JSON
{
"ID": null,
"ResCode": 100,
"ResMessage": "هیچ آیتم جدیدی در پایگاه داده ذخیره نشد",
"ResPos": null,
"ResData": {
"data": [
{
"data": {
"id": "2994LUZUWL",
"type": "SHEBAD_ID"
},
"sendDate": "2021-11-02T20:00:16.827"
},
{
"data": {
"id": "2992CQVAE1",
"type": "SHEBAD_ID"
},
"sendDate": "2021-11-02T18:43:12.857"
},
{
"data": {
"id": "Z2005KUU6ZB",
"type": "SHEBAD_ID"
},
"sendDate": "2021-11-02T18:51:36.107"
},
{
"data": {
"id": "Z29914MM2WL",
"type": "SHEBAD_ID"
},
"sendDate": "2021-11-02T19:21:08.607"
}
],
"message": "",
"succeeded": true
}
}
How can I access to ID values inside this JSON?
Delphi 6 user here, but same thing really..
You need to have uLKJson installed, which I assume you have.
You need to parse each element as one of the uLKJSON types:
jsBase, jsNumber, jsString, jsBoolean, jsNull, jsList, jsObject
You have
{ <<< root = object
"ResData": { <<< ResData = object
"data": [ <<< data = list
{ <<< element[0] = object
"data": { <<< data = object
"id": "2994LUZUWL", <<< id = string. This is what you want.
...
In jq, this would be selection string: ".ResData.data[].data.id".
The program to parse the data could look something like this:
program testparse;
{$APPTYPE CONSOLE}
uses
SysUtils, // for writing on console
ulkjson; // to parse JSON
var
vJsonObj: TlkJsonObject;
jo : TlkJSONstreamed;
listofelements: TlkJSONlist;
i : integer;
// {
// "data": {
// "id": "2994LUZUWL",
// "type": "SHEBAD_ID"
// },
// "sendDate": "2021-11-02T20:00:16.827"
// },
// function for simplicity
//
function get_key( j : TlkJSONobject) : string;
begin
try
result := j.Field['data'].Field['id'].Value;
except
result := 'N/A'; // if the id key is not there..
end;
end;
begin
jo := TlkJSONstreamed.Create;
try
vJsonObj := jo.LoadFromFile('jsontext.txt') as TlkJsonObject;
listofelements := vJsonObj.Field['ResData'].Field['data'] as TlkJSONlist;
for i := 0 to listofelements.Count-1 do
begin
writeln('Key no ' + inttostr(i) + '=' +
get_key(listofelements.Child[i] as TlkJSONobject)
);
end;
finally
jo.free;
end;
end.
Result:
Key no 0=2994LUZUWL
Key no 1=2992CQVAE1
Key no 2=Z2005KUU6ZB
Key no 3=Z29914MM2WL
Alternative library
Alternatively JSONTools, originally from Lazarus can be used. I have modified and uploaded a version for Delphi 6/7.
Using this unit:
writeln('-- doing it with JSONTools');
N := TJsonNode.Create;
n.LoadFromFile('jsontext.txt'); // will automatically do a parse
n:= n.Find('/ResData/data'); // get the array
for i := 0 to n.Count-1 do
begin
writeln(
'Key no ' + inttostr(i) + '=' +
n.Child(i).Find('data/id').AsString);
end;
n.Free;
Adding to a combobox
As the question was for how to add to a combobox: this is simple. First clear the combobox, then for each item do an "additem".
Related
I'm trying to extract Protobuf custom options from a FileDescriptorSet generated by the protoc compiler. I'm unable to do so using protoreflect. So, I tried to do so using the protojson library.
PS : Importing the Go-generated code is not an option for my use case.
Here's the Protobuf Message I'm testing with :
syntax = "proto3";
option go_package = "./protoze";
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
string Meta = 50000;
}
extend google.protobuf.FileOptions {
string Food = 50001;
}
option (Food) = "cheese";
message X {
int64 num = 1;
}
message P {
string Fname = 1 [json_name = "FNAME"];
string Lname = 2 [json_name = "0123", (Meta) = "Yo"];
string Designation = 3;
repeated string Email = 4;
string UserID = 5;
string EmpID = 6;
repeated X z = 7;
}
// protoc --go_out=. filename.proto
Here's how far I got :
package main
import (
"fmt"
"io/ioutil"
"os/exec"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
)
func main() {
exec.Command("protoc", "-oBinaryFile", "1.proto").Run()
Fset := descriptorpb.FileDescriptorSet{}
byts, _ := ioutil.ReadFile("File")
proto.Unmarshal(byts, &Fset)
byts, _ = protojson.Marshal(Fset.File[0])
fmt.Println(string(byts))
}
And here's the output JSON
{
"name": "1.proto",
"dependency": [
"google/protobuf/descriptor.proto"
],
"messageType": [
{
"name": "X",
"field": [
{
"name": "num",
"number": 1,
"label": "LABEL_OPTIONAL",
"type": "TYPE_INT64",
"jsonName": "num"
}
]
},
{
"name": "P",
"field": [
{
"name": "Fname",
"number": 1,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "FNAME"
},
{
"name": "Lname",
"number": 2,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "0123",
"options": {}
},
{
"name": "Designation",
"number": 3,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "Designation"
},
{
"name": "Email",
"number": 4,
"label": "LABEL_REPEATED",
"type": "TYPE_STRING",
"jsonName": "Email"
},
{
"name": "UserID",
"number": 5,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "UserID"
},
{
"name": "EmpID",
"number": 6,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"jsonName": "EmpID"
},
{
"name": "z",
"number": 7,
"label": "LABEL_REPEATED",
"type": "TYPE_MESSAGE",
"typeName": ".X",
"jsonName": "z"
}
]
}
],
"extension": [
{
"name": "Meta",
"number": 50000,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"extendee": ".google.protobuf.FieldOptions",
"jsonName": "Meta"
},
{
"name": "Food",
"number": 50001,
"label": "LABEL_OPTIONAL",
"type": "TYPE_STRING",
"extendee": ".google.protobuf.FileOptions",
"jsonName": "Food"
}
],
"options": {
"goPackage": "./protoze"
},
"syntax": "proto3"
}
So, data about my custom options showed up in the extensions. But what I really wanted was the value of those Custom Options in the "options" as well. (Which in my case was (Food) = "Cheese" and I want Cheese)
Can someone tell me how I can extract my custom options from the FileDescriptorSet using Protoreflect or by using Protojson.
I tried a lot to try and extract it using Protoreflect but failed !
Although not specifically an answer to how to get the custom options in a generated JSON, I believe I have an answer to what sounds like your underlying question: how to access the custom options without loading the generated Go code. This is thanks to dsnet's answer to my question on the golang issues board. Needless to say all the credit for this tricky solution goes to him. The punchline is to Marshal and then Unmarshal the options using a runtime-populated protoregistry.Types that actually knows about the custom options.
I made a complete demonstration of this approach working in this repo, and the key section (all the guts of which come from dsnet's example) is here:
func main() {
protogen.Options{
}.Run(func(gen *protogen.Plugin) error {
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
// The type information for all extensions is in the source files,
// so we need to extract them into a dynamically created protoregistry.Types.
extTypes := new(protoregistry.Types)
for _, file := range gen.Files {
if err := registerAllExtensions(extTypes, file.Desc); err != nil {
panic(err)
}
}
// run through the files again, extracting and printing the Message options
for _, sourceFile := range gen.Files {
if !sourceFile.Generate {
continue
}
// setup output file
outputfile := gen.NewGeneratedFile("./out.txt", sourceFile.GoImportPath)
for _, message := range sourceFile.Messages {
outputfile.P(fmt.Sprintf("\nMessage %s:", message.Desc.Name()))
// The MessageOptions as provided by protoc does not know about
// dynamically created extensions, so they are left as unknown fields.
// We round-trip marshal and unmarshal the options with
// a dynamically created resolver that does know about extensions at runtime.
options := message.Desc.Options().(*descriptorpb.MessageOptions)
b, err := proto.Marshal(options)
if err != nil {
panic(err)
}
options.Reset()
err = proto.UnmarshalOptions{Resolver: extTypes}.Unmarshal(b, options)
if err != nil {
panic(err)
}
// Use protobuf reflection to iterate over all the extension fields,
// looking for the ones that we are interested in.
options.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if !fd.IsExtension() {
return true
}
outputfile.P(fmt.Sprintf("Value of option %s is %s",fd.Name(), v.String()))
// Make use of fd and v based on their reflective properties.
return true
})
}
}
return nil
})
}
// Recursively register all extensions into the provided protoregistry.Types,
// starting with the protoreflect.FileDescriptor and recursing into its MessageDescriptors,
// their nested MessageDescriptors, and so on.
//
// This leverages the fact that both protoreflect.FileDescriptor and protoreflect.MessageDescriptor
// have identical Messages() and Extensions() functions in order to recurse through a single function
func registerAllExtensions(extTypes *protoregistry.Types, descs interface {
Messages() protoreflect.MessageDescriptors
Extensions() protoreflect.ExtensionDescriptors
}) error {
mds := descs.Messages()
for i := 0; i < mds.Len(); i++ {
registerAllExtensions(extTypes, mds.Get(i))
}
xds := descs.Extensions()
for i := 0; i < xds.Len(); i++ {
if err := extTypes.RegisterExtension(dynamicpb.NewExtensionType(xds.Get(i))); err != nil {
return err
}
}
return nil
}
How do I parse the following Json in Delphi?
This is my first post, and I've taken care to search as much as I can before asking this question,
so please kindly let me know if I posted wrongly in any way.
I would like to get the value of "name_of_centre" in the "records" array
Thank you for any kind assistance.
procedure TForm1.Button1Click(Sender: TObject);
var
i : integer;
jsonRoot: TJSONValue;
jsonObj: TJSONObject;
jsonArr: TJSONArray;
begin
jsonRoot := TJSONObject.ParseJSONValue(memo2.Lines.text);
try
jsonObj := jsonRoot as TJSONObject;
jsonObj := jsonObj.GetValue('result') as TJSONObject;
jsonArr := jsonObj.GetValue('records') as TJSONArray;
showmessage( jsonArr.Count.ToString ); // works ok
for i := 0 to jsonArr.Count - 1 do
begin
jsonObj := jsonArr.Items[i] as TJSONObject;
showmessage( jsonObj.GetValue('name_of_centre').Value ); // error here
end;
finally
jsonRoot.Free;
end;
end;
I've checked out
Delphi parsing a Json with multiple array types?
How to parse this json data in Delphi 10 Seattle?
(especially this one)
and a few other links... but the JSON format seems different.
Any advice?
{
"help": "testing",
"success": true,
"result": {
"resource_id": "data_resource",
"fields": [
{
"type": "int4",
"id": "_id"
},
{
"type": "text",
"id": "name_of_centre"
},
{
"type": "text",
"id": "location_of_centre"
},
{
"type": "text",
"id": "type_of_centre"
},
{
"type": "text",
"id": "owner"
},
{
"type": "numeric",
"id": "no_of_outlets"
},
{
"type": "numeric",
"id": "no_of_branches"
}
],
"records": [
{
"location_of_centre": "Kings Road",
"no_of_outlets": "12",
"no_of_branches": "0",
"name_of_centre": "Kings Road Centre",
"type_of_centre": "HC",
"owner": "Private",
"_id": 1
},
{
"location_of_centre": "Queens",
"no_of_outlets": "14",
"no_of_branches": "1",
"name_of_centre": "Queens Centre",
"type_of_centre": "HC",
"owner": "Public",
"_id": 2
}
],
"_links": {
"start": "ignore",
"next": "ignore2"
},
"limit": 2,
"total": 10
}
}
Thanks for the many replies.
Olivier : I've included the error in this amended code.
Peter : I tried using
jsonRoot.GetValue('result.records[0].name_of_centre')
and it does give me the value of name_of_centre. Good start.
But I'm hoping to get this code to give me the number of items in Array and I iterate the array, instead of hard-code. Thanks.
Remy : strangely though, it works today. It doesn't get Invalid Typecast
at showmessage( jsonObj.GetValue('name_of_centre').Value );
fpiette : I use Delphi 10.3 RIO.
Thanks to everyone for replying.
Is there a need to use jsonRoot.Free; -- i saw this in a posting on stackoverflow.com... How about jsonObj.Free?
jsonRoot := TJSONObject.ParseJSONValue(memo2.Lines.text);
try
jsonObj := jsonRoot as TJSONObject;
jsonObj := jsonObj.GetValue('result') as TJSONObject;
showmessage( jsonObj.ToString );
jsonArr := jsonObj.GetValue('records') as TJSONArray;
showmessage( jsonArr.Count.ToString );
for i := 0 to jsonArr.Count - 1 do
begin
jsonObj := jsonArr.Items[i] as TJSONObject;
if jsonObj .GetValue('name_of_centre').Value = null then
showmessage('null');
// previously had an Invalid Typecast
showmessage( jsonObj.GetValue('name_of_centre').Value );
end;
finally
jsonRoot.Free;
end;
I want to parse a small JSON file with a nested structure. I am mainly interested in the "name" value, but as there are several name values throughout the document, it would be convenient to have a level depth identifier of some kind.
{
"status": "Tomato",
"name": "ThisIsWhatIwant",
"params": [
{
"name": "THatsNoGood",
"values": [
{
"value": ""
}
]
},
{
"name": "dontlikeiteither",
"values": [
{
"value": ""
}
]
},
{
"name": "Pffff",
"values": [
{
"value": ""
}
]
},
{
"name": "Trump",
"values": [
{
"value": ""
}
]
},
{
"name": "Obama",
"values": [
{
"value": ""
}
]
},
{
"name": "Jackson5",
"values": [
{
"value": ""
}
]
}
],
"NewEden": false,
"Potatoes": []
}
]
Delphi code:
procedure TFmain.json_extract_names(filename: string);
var jsonStr: string;
sr: TStringReader; jtr: TJsonTextReader; s: string;
sl: TSTringList;
I: Integer;
begin
jsonStr := TFile.ReadAllText(FileOpenDialog1.FileName);
sl := Tstringlist.Create;
sr := TStringReader.Create(jsonStr);
try
jtr := TJsonTextReader.Create(sr);
try
while jtr.Read do
begin
s := JsonTokenToString(jtr.TokenType);
if jtr.TokenType = TJsonToken.PropertyName then
begin
if jtr.Value.ToString = 'name' then
begin
jtr.Read;
sl.Add(jtr.Value.AsString);
end
else if jtr.TokenType = TJsonToken.EndObject then
begin
exit;
end;
end;
end;
finally
jtr.Free;
end;
for I := 0 to sl.Count-1 do
begin
ComboBoxsearch.Items.Add(sl[i]);
end;
finally
sr.Free;
sl.Free;
end;
End;
The above code works and my stringlist contains all name values - but I only need the name of the first level! Is there any way to only get the first level name? (in my example JSON the desired result would be: ThisIsWhatIwant)
TJsonTextReader has a Depth property:
Gets the depth of the current token in the JSON document.
Depth returns an integer that represents the nested level of the current token.
For example:
procedure TFmain.json_extract_names(filename: string);
var
jsonStr: string;
sr: TStringReader;
jtr: TJsonTextReader;
sl: TStringList;
begin
jsonStr := TFile.ReadAllText(filename);
sl := TStringList.Create;
try
sr := TStringReader.Create(jsonStr);
try
jtr := TJsonTextReader.Create(sr);
try
while jtr.Read do
begin
if (jtr.Depth = 1) and
(jtr.TokenType = TJsonToken.PropertyName) and
(jtr.Value.ToString = 'name') then
begin
jtr.Read;
sl.Add(jtr.Value.AsString);
end;
end;
finally
jtr.Free;
end;
finally
sr.Free;
end;
ComboBoxsearch.Items.AddStrings(sl);
finally
sl.Free;
end;
End;
I have a JSON file that looks like this and I'm trying to decode it, but with no luck:
[
{
"FirstName": "Kim",
"Surname": "Jensen"
},
{
"FirstName": "Amery",
"Surname": "Mcmillan"
},
{
"FirstName": "Denton",
"Surname": "Burnett"
}
...
]
Using uJson with Delphi 2007, I know how to extract the data when the array has a name like this:
{
"Names": [
{
"FirstName": "Kim",
"Surname": "Jensen"
},
{
"FirstName": "Amery",
"Surname": "Mcmillan"
},
{
"FirstName": "Denton",
"Surname": "Burnett"
}
...
]
}
var
json: TJSONObject;
Text: String;
i: Integer;
begin
json := TJSONObject.create(jsontext);
for i:=0 to json.getJSONArray('Names').Length -1 do
begin
Text := json.getJSONArray('Names').getJSONObject(i).optString('FirstName');
...
end;
end;
But, this array has no name, and I have tried almost everything I can think of and still this simple thing has taking me hours to figure out.
In the JSON you are having trouble with, the top-level data is the array, so you need to parse it using TJSONArray instead of TJSONObject.
var
json: TJSONArray;
Text: String;
i: Integer;
begin
json := TJSONArray.create(jsontext);
try
for i := 0 to json.Length-1 do
begin
Text := json.getJSONObject(i).optString('FirstName');
...
end;
finally
json.Free;
end;
end;
I have this Json
{
"Sucess": true,
"Msg": "OK",
"Ret": {
"First": 0,
"Next": true,
"Total": 60,
"Itens": [
{
"ID": 212121,
"Name": "uuuuuuuuuuuuuuuuuuuuuuuu",
"LcID": 9898,
"Oclao": false,
"Lal": {
"ID": 12202,
"Name": "pppppppppppppppppp",
"Pais": "Brasil",
"Dtc": 0.0
},
"Subtipo": {
"ID": 7458,
"Desc": "mnmnmnmnn"
},
"Tipo": {
"Sit": "cor1",
"Sitrm": 0,
"Name": "Shsdfow"
},
"Qtde": 0,
"Qntcoes": 0,
"Pubum": "adfsdfsdfs",
"Evias": {
"arq": {
"Mo": [
"site.com"
],
"Moir": [
"site.com"
]
}
}
},
{
"ID": 9797878,
"Name": "uuuuuuuuuuuuuuuuuuuuuuuu",
"LcID": 9898,
"Oclao": false,
"Lal": {
"ID": 12332,
"Name": "pppppppppppppppppp",
"Pais": "Brasil",
"Dtc": 0.0
},
"Subtipo": {
"ID": 7458,
"Desc": "mnmnmnmnn"
},
"Tipo": {
"Sit": "cor1",
"Sitrm": 0,
"Name": "Shsdfow"
},
"Qtde": 0,
"Qntcoes": 0,
"Pubum": "adfsdfsdfs",
"Evias": {
"arq": {
"Mo": [
"site.com"
],
"Moir": [
"site.com"
]
}
}
}
]
}
}
however, I can only take the values of the first items "Sucess, Msg and Ret"
I'm doing as follows
var
JSONValue, jv: TJSONValue;
joName: TJSONObject;
data: TBytes;
sHtmlResp, sTemp : String;
begin
sHtmlResp := '[' + sHtmlResp + ']';
data := TEncoding.ASCII.GetBytes(sHtmlResp);
JSONValue := TJSONObject.ParseJSONValue(data, 0);
for jv in JSONValue as TJSONArray do
begin
joName := jv as TJSONObject;
sTemp:= joName.Get('Msg').JSONValue.Value;
end;
end;
sHtmlResp contains the string json.
I've tried some other ways to get the most unsuccessful field, how do I get the sub-items as "Ret", "Items" and so on.
A detail, had to add '[' ']' in the JSON string I get, otherwise I can not get even the first fields.
Thank!
Ret is a subobject so you need to access it as such. It has its own values and an array of subobjects, and so on.
Try this:
var
joName, joRet, joItem: TJSONObject;
joItems: TJSONArray;
sHtmlResp, sMsg: String;
bSuccess: Boolean;
begin
sHtmlResp := ...; // original JSON without added braces around it
joName := TJSONObject.ParseJSONValue(sHtmlResp) as TJSONObject;
bSuccess := joName.GetValue('Success') is TJSONTrue;
// if you are using Delphi 10 Seattle or later, you can use this instead:
// bSuccess := (joName.GetValue('Success') as TJSONBool).AsBoolean;
sMsg := joName.GetValue('Msg').Value;
joRet := joName.GetValue('Ret') as TJSONObject;
// use joRet.GetValue() as needed ...
joItems := joRet.GetValue('Itens') as TJSONArray;
for i := 0 to joItems.Count-1 do
begin
joItem := joItems.Items[i] as TJSONObject;
// use joItem.GetValue() as needed ...
end;
end;
If each object/sub-object have unique ObjectID (name).
The simple way is using this function
function ExtractJsonParm(parm:string;h:string):string;
var r:integer;
begin
r:=pos('"'+Parm+'":',h);
if r<>0 then
result := copy(h,r+length(Parm)+4,pos(',',copy(h,r+length(Parm)+4,length(h)))-1)
else
result:='';
end;
Usage:
begin
Writeln(ExtractJsonParm('First',Value));
Writeln(ExtractJsonParm('Next',Value));
Writeln(ExtractJsonParm('Total',Value));
end;
Outputs:
0
true
60
Using jsonDoc it would be
JSON(sHtmlResp)['Msg']
optionally enclosed in VarToStr to enforce type (and also silently convert Null to empty string value).
As noted in the comments by Remy: no need to enclose in [] as this enforces the outer array.