I am trying to parse the following Json document:
[
{"EventType":49,"Code":"234","EventDate":"20050202", "Result":1},
{"EventType":48,"Code":"0120","EventDate":"20130201", "Group":"g1"}
]
I use the following code:
TJSONObject* jsonread0 = (TJSONObject*) TJSONObject::ParseJSONValue(TEncoding::ASCII->GetBytes(Memo1->Lines->Text), 0);
for(int i=0;i<jsonread0->Size();i++)
{
TJSONPair* pair = jsonread0->Get(i);
At this point, pair.JsonValueis NULL. What do I need to do to read the values?
You are not casting the JSON String properly, you must cast as an TJSONArray and then iterate over the elements.
try these samples
Delphi
{$APPTYPE CONSOLE}
uses
DBXJSON,
System.SysUtils;
Const
StrJson =
'['+
'{"EventType":49,"Code":"234","EventDate":"20050202", "Result":1},'+
'{"EventType":48,"Code":"0120","EventDate":"20130201", "Group":"g1"}'+
']';
procedure ParseJson;
var
LJsonArr : TJSONArray;
LJsonValue : TJSONValue;
LItem : TJSONValue;
begin
LJsonArr := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(StrJson),0) as TJSONArray;
for LJsonValue in LJsonArr do
begin
for LItem in TJSONArray(LJsonValue) do
Writeln(Format('%s : %s',[TJSONPair(LItem).JsonString.Value, TJSONPair(LItem).JsonValue.Value]));
Writeln;
end;
end;
begin
try
ParseJson;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
C++ Builder
#include <vcl.h>
#include <windows.h>
#pragma hdrstop
#pragma argsused
#include <tchar.h>
#include <stdio.h>
#include <DBXJSON.hpp>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
TJSONArray* LJsonArr = (TJSONArray*)TJSONObject::ParseJSONValue(
BytesOf((UnicodeString)"[{\"EventType\":49,\"Code\":\"234\",\"EventDate\":\"20050202\", \"Result\":1}, {\"EventType\":48,\"Code\":\"0120\",\"EventDate\":\"20130201\", \"Group\":\"g1\"}]"),0);
int size = LJsonArr->Size();
for (int i = 0; i < size; ++i)
{
TJSONValue* LJsonValue = LJsonArr->Get(i);
TJSONArray* LJsonArr2 = (TJSONArray*)LJsonValue;
int size2 = LJsonArr2->Size();
for (int j = 0; j < size2; ++j)
{
TJSONValue* LItem = LJsonArr2->Get(j);
TJSONPair* LPair = (TJSONPair*)LItem;
printf("%s %s \n", (UTF8String )(LPair->JsonString->Value()).c_str(), (UTF8String )(LPair->JsonValue->Value()).c_str());
}
}
std::cin.get();
return 0;
}
This will return
EventType : 49
Code : 234
EventDate : 20050202
Result : 1
EventType : 48
Code : 0120
EventDate : 20130201
Group : g1
You have an invalid type cast, so what you're seeing is undefined behavior. A null result is just one of the many possible outcomes you could expect from this code. The ParseJSONValue function in this case should return a TJsonArray, not a TJsonObject. Although both classes have Get methods, they're not interchangeable.
The array's Get method returns a TJsonValue, not a TJsonPair. For this particular data, you can type-cast the value to TJsonObject because your data represents an array of two objects.
Use dynamic_cast or Delphi's as operator to cast from one class to another.
dbExpress JSON parser was told to be heavyweight and sometimes problematic.
Perhaps you can choose some of the number of 3rd-party parsers, for example this shows reading array: http://code.google.com/p/superobject/wiki/first_steps
you can get an array from a JSON string also using the JSonCBuilderBlog Library for C++Builder (free and open source):
UnicodeString JSONSource =
"[{\"EventType\":49,\"Code\":\"234\",\"EventDate\":\"20050202\", \"Result\":1},"
"{\"EventType\":48,\"Code\":\"0120\",\"EventDate\":\"20130201\",\"Group\":\"g1\"}]";
int Type;
UnicodeString Code;
UnicodeString Date;
int Result;
TMetaObject MyArray;
MyArray.Decode(JSONSource);
for(int i=0; i < MyArray.Count(); i++)
{
Type = MyArray[i]["EventType"];
Code = MyArray[i]["Code"];
Date = MyArray[i]["EventDate"];
}
The syntax is very simple, see the following link as reference: JSONCBuilderBlog library.
Related
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.
Let's say I have a program with the following declaration:
std::vector<std::vector<std::vector<std::string> *> > s;
Now I would like to tokenize this, cut it in parts:
std vector
std vector
std vector pointer
std string
When going through the source-code, this example will give me a VarDecl. If I understood it correctly, this VarDecl contains the whole declaration. Now I think the next step is invoking getType() on that VarDecl. But then what? I expected a method that would return e.g. an iterator or so.
Below you'll see what I have. This works fine for e.g. "int i" or "const char *const i = NULL;" even for "const std::string l = "12";" but "std::vector *> > s;" results in an "int"?(!?).
void dissectType(ASTContext *const Context, const QualType x)
{
if (x.isNull())
return;
QualType type = x.getNonReferenceType();
for(;!type.isNull();) {
if (type.hasQualifiers()) {
Qualifiers q = type.getQualifiers();
if (q.hasConst())
printf("const ");
if (q.hasVolatile())
printf("volatile ");
if (q.hasRestrict())
printf("restrict ");
}
const Type *t = type.getTypePtr();
if (!t) {
printf("null?\n");
break;
}
else if (t -> isPointerType())
printf("* ");
else if (t -> isFundamentalType()) {
std::string curType = type.getUnqualifiedType().getAsString();
printf("%s\n", curType.c_str());
break; // should be last entry in this chain
}
type = type->getPointeeType();
}
}
I want to open a 50 MB binary file and read only the last 4 bytes and convert it to string for some purpose.
The only way I found to do it now is using LoadStringFromFile, to load the file entirely on the memory then copy that last 4 bytes, however this method is very slow because the binary file is heavy.
Is there any better way to do it in Inno Setup script?
Update: This is a final working function that I edited from Martin Prikryl's answer
function readlast4byte() : AnsiString;
var
Stream: TFileStream;
Buffer: string;
Count: Integer;
Index: Integer;
begin
Count := 4;
Stream := TFileStream.Create('C:\test.txt', fmOpenRead);
try
Stream.Seek(-Count, soFromEnd);
SetLength(Buffer, 1);
SetLength(Result, Count);
for Index := 1 to Count do
begin
Stream.ReadBuffer(Buffer, 1);
Result[Index] := Chr(Ord(Buffer[1])) ;
end;
finally
Stream.Free;
end;
end;
Update 2: Also this is another great working function that written by TLama and should mark as answer too:
[Code]
#IFNDEF Unicode
#DEFINE CharSize 1
#ELSE
#DEFINE CharSize 2
#ENDIF
type
TSeekOrigin = (
soBeginning,
soCurrent,
soEnd
);
#IFDEF UNICODE
function BufferToAnsi(const Buffer: string): AnsiString;
var
W: Word;
I: Integer;
begin
SetLength(Result, Length(Buffer) * 2);
for I := 1 to Length(Buffer) do
begin
W := Ord(Buffer[I]);
Result[(I * 2)] := Chr(W shr 8); // high byte
Result[(I * 2) - 1] := Chr(Byte(W)); // low byte
end;
end;
#ENDIF
function ReadStringFromFile(
const FileName: string; Origin: TSeekOrigin; Offset, Length: Integer;
var S: AnsiString): Boolean;
var
Buffer: string;
Stream: TFileStream;
begin
Result := True;
try
Stream := TFileStream.Create(FileName, fmOpenRead);
try
Stream.Seek(Offset, Ord(Origin));
SetLength(Buffer, Length div {#CharSize});
Stream.ReadBuffer(Buffer, Length);
#IFNDEF UNICODE
S := Buffer;
#ELSE
S := BufferToAnsi(Buffer);
#ENDIF
finally
Stream.Free;
end;
except
Result := False;
end;
end;
You can use TFileStream support class:
TStream = class(TObject)
function Read(Buffer: String; Count: Longint): Longint;
function Write(Buffer: String; Count: Longint): Longint;
function Seek(Offset: Longint; Origin: Word): Longint;
procedure ReadBuffer(Buffer: String; Count: Longint);
procedure WriteBuffer(Buffer: String; Count: Longint);
function CopyFrom(Source: TStream; Count: Longint): Longint;
property Position: Longint; read write;
property Size: Longint; read write;
end;
THandleStream = class(TStream)
constructor Create(AHandle: Integer);
property Handle: Integer; read;
end;
TFileStream = class(THandleStream)
constructor Create(Filename: String; Mode: Word);
end;
Use the .Seek(-4, soFromEnd) property to seek the read pointer to the desired position.
A complication is that the TStream works with characters not bytes, so you have to convert Unicode strings that your read back to bytes.
When reading just four bytes, it's way easier to read byte by byte, preventing any multibyte conversions:
procedure ReadFileEnd;
var
Stream: TFileStream;
Buffer: string;
Count: Integer;
Index: Integer;
begin
Count := 4;
Stream := TFileStream.Create('my_binary_file.dat', fmOpenRead);
try
Stream.Seek(-Count, soFromEnd);
SetLength(Buffer, 1);
for Index := 1 to Count do
begin
Stream.ReadBuffer(Buffer, 1);
Log(Format('Byte %2.2x: %2.2x', [Index, Ord(Buffer[1])]));
end;
finally
Stream.Free;
end;
end;
Here's a generic alternative from by #TLama that efficiently works for arbitrary large read:
https://pastebin.com/nzGEdXVj
By the way, for me LoadStringFromFile function seems to be efficient enough to load 50 MB file. It takes only 40 ms.
I have to place a JSON string to a HTTP request's body. One of the string value must be a JSON array. This is how I tried:
uses
DBXJSON;
const
cContent = 'hello world';
var
LJSONObject: TJSONObject;
x: TBytes;
i: integer;
Temp: string;
begin
LJSONObject:= TJSONObject.Create;
LJSONObject.AddPair('id1', 'value1');
LJSONObject.AddPair('id2', 'value2');
LJSONObject.AddPair('id2', 'value3');
x:= TEncoding.ANSI.GetBytes(cContent);
Temp:= '';
for i := 0 to Length(x) - 1 do
Temp:= Temp + IntToStr(x[i]) + ',';
Delete(Temp, Length(Temp), 1);
LJSONObject.AddPair('id4', '[' + Temp + ']');
ShowMessage(LJSONObject.ToString);
end;
This one is not working, because the value will be encapsulated in double quotes. What is the proper way to pass an array value to the JSONObject?
You are passing a string rather than an array. Hence the result you observe. As a rule, if you find yourself assembling the JSON manually, you are doing it wrong.
Pass an array:
var
arr: TJSONArray;
b: Byte;
....
arr := TJSONArray.Create;
for b in TEncoding.ANSI.GetBytes(cContent) do
arr.Add(b);
LJSONObject.AddPair('id4', arr);
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.