I am using Delphi Xe5 and I have a component that essentially uses IDTCPCLient (sockets) to talk with are server and retrieve data in the form of JSON. I have spared you all the connection code , etc. It works. The returned JSon works as well. The trouble I am having is converting my JSON into a StringList, which I then use to write out a list of values to a listbox and store the rest of the JSON data objects in the TSTrings OBjects property.
I have a number of funny things happen.
1.) I can't for the life of me get the List property to work properly. I use this list to store my JSON. A string value and then the entire object for each item in the list. You will notice that in the JSONToStringList method, I clear the stringlist (it is commented out, becuase when it isn't, my program hangs)
2.) I get dupplicate values in my list after calling the method more than once for multiple JSON sets needed
TConnector = class(TComponent)
private
{ Private declarations }
FList: TStrings;
procedure SetList(const Value: TStrings);
protected
{ Protected declarations }
public
{ Public declarations }
Constructor Create( AOwner : TComponent ); override;
Destructor Destroy; Override;
Procedure GenerateJSON;
Procedure JSONToStringList(aJSonKey: String);
published
{ Published declarations }
property List: TStrings Read FList Write SetList;
end;
Constructor TConnector.Create(AOwner: TComponent);
begin
inherited;
FList:= TStringList.Create(True);
end;
destructor TConnector.Destroy;
begin
if FList <> nil then
FreeAndNil(FList);
inherited;
end;
Procedure TConnector.GenerateJSON;
begin
if ResponseStream<>nil then
Begin
FreeAndNil(ResponseJSON_V);
ResponseJSON_V := TJSONObject.ParseJSONValue(StreamToArray(ResponseStream),0) as TJSONValue;
End;
end;
procedure TConnector.JSONToStringList(aJSonKey: String);
Var
zLJsonValue : TJSONValue;
zLJSONArray: TJSONArray;
zLJsonObject : TJSONObject;
zI : Integer;
begin
if ResponseJSON_V is TJSONArray then
begin
zLJSONArray:= ResponseJSON_V as TJSONArray;
zLJsonObject := zLJSONArray.Get(0) as TJSONObject;
end
else
if ResponseJSON_V is TJSONObject then
begin
zLJSONArray:= nil;
zLJsonObject := ResponseJSON_V as TJSONObject;
end
else
Exit;
if zLJSONArray<>nil then
begin
***//FList.Clear;***
for zLJsonValue in zLJSONArray do
begin
zLJsonObject := zLJsonValue as TJSONObject;
for zI := 0 to zLJsonObject.Size-1 do
begin
if zLJsonObject.Get(zI).JsonString.Value = aJSonKey then
begin
FList.AddObject(zLJsonObject.Get(zI).JSONValue.Value, zLJsonObject);
end;
end;
end;
end
else
begin
FList.Clear;
for zI := 0 to zLJsonObject.Size-1 do
begin
if zLJsonObject.Get(zI).JsonString.Value = aJSonKey then
FList.AddObject(zLJsonObject.Get(zI).JSONValue.Value, TJSONPair(zLJsonObject.Get(zI)));
end;
end;
end;
I hope this is all understandable. Please let me know if you need to see more. Please feel free to correct anything else you see bad in my code. I am always learning :) - thank you for your help
If FList.Clear hangs then it's most likely memory corruption issue. First two thing I would suspect is that you have not called a constructor or that part of the memory has been overwritten by something else.
Can the duplicate values in the list be cause by the fact that you commented out FList.Clear. Anyway, I suggest using a debugger to see what goes in to the list or log everything added to/removed from the list. This should give you the idea where unneeded values in the list are coming from.
As a general advice you don't need to check if object is not nil before freeing it. The check is made in Free (or in the Free part of the FreeAndNil) anyway.
It turns out , that the FList property should not have been published, but rather made public instead. Which makes since, cause the list is only filled and emptied at runtime, never at design time.
TLiveConnector = class(TComponent)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
List: TStrings;
published
end;
Not sure exactly why that is the case, but it seem to work just fine now
Calling List.Clear instead of FList.Clear;
your code cannot work ... U define a param called "aJSonKey" and u call it "ResponseJSON_V " into the procedure.
Moreover u call this "ResponseJSON_V" into GenerateJSON proc when there is no property called that way.
Related
I would like to use the REST.JSON Librairy to convert an Object to a JSON-String and back, but i'm encountering some problems.
Let's say the classes i would like to convert is "TFooChild1" and "TFooChild2" which both descend from "TFoo".
The classes look like this:
TFoo = class
protected
Name: string;
Value: Double;
end;
TFooChild1 = class(TFoo)
private
Limit: Double;
end;
TFooChild2 = class(TFoo)
private
Limit: Double;
WorkerID: Integer;
end;
Creating and converting to JSON would look something like this:
var
Foo: TFoo;
s: string;
begin
Foo := TFooChild1.Create;
Foo.Name:= '...';
... //assign all the Fields
s := TJson.ObjectToJsonString(Foo);
Foo.Free;
//conversion to string is correct...
Foo := TJson.JsonToObject<TFoo>(s, []);
//Field "Limit" is lost
end
I'm aware that this (TJson.JsonToObject<TFoo>(s, [])) would never return the type TFooChild1, but that's exactly what i need.
When you convert back to an Object, the Fields of the child classes are lost. How do I avoid this? I can't do JsonToObject<TFooChild1> because I don't know if it's gonna be Child1 or Child2.
Any hints?
I've googled if there's maybe a ConvertOption that would include Type information, but i haven't found anything
Add Rest.JsonReflect to your uses class as you will need to use the [JsonReflect(ctObject,ctObject,TFooInterceptor)] attribute attached to where you are referencing the TFoo class. Then you will have to code a TFooInterceptor object to intercept and handle the appropriate conversion. I would code the ObjectReverter and ObjectConverter overrides and start coding from there. Your TFooInterceptor should descend from TJSONInterceptor. Some examples are in the source REST.Json.Interceptors.pas.
I have a class in Delphi which I export in jsonmarshalled file.
I am skipping some Fields using the JSONMarshalledAttribute, which resides in the unit: REST.JSON.Types. More literature here
[JSONMarshalledAttribute(False)]
Field1: double;
[JSONMarshalledAttribute(False)]
Field2: double;
So far this works great.
My question is: Can I change the JSONMarshalledAttribute to True during runtime?
EDIT 1:
As requested here is the code:
Suppose we have a Form2:TForm and within the form as follows...:
Interface(I am skipping the attributes of the form....)
type
TmyClass = class(Tobject)
private
[JSONMarshalledAttribute(false)]
FName: string;
FVal1: double;
public
property Name: string read FName write FName;
property Val1: double read FVal1 write FVal1;
end;
and then in the implementation:
procedure TForm2.Button2Click(Sender: Tobject);
var
LArray: TJSONArray;
begin
MyClass := TmyClass.Create;
MyClass.name := 'myNAme';
LArray := myMarshaler(MyClass, 'FName', True);
end;
and the actual function that returns a TJSONArray:
function TForm2.myMarshaler(myclass: TmyClass; Field: string; Marshal: Boolean)
: TJSONArray;
var
Marshaler: TJSONMarshal;
JSONObject: TJSONObject;
LArray: TJSONArray;
begin
Marshaler := TJSONMarshal.Create(TJSONConverter.Create);
try
Marshaler.RegisterJSONMarshalled(myclass, Field,Marshal);
// Marshaler.DateFormat := jdfUnix;
JSONObject := Marshaler.Marshal(myclass) as TJSONObject;
LArray := TJSONArray.Create;
LArray.AddElement(JSONObject);
result := LArray;
finally
FreeAndNil(Marshaler);
end;
end;
That will not work because Marshaler.RegisterJSONMarshalled requires a TClass as an argument type, but I want to input my own custom classes which are derived from TObject.
and this is the error:
[dcc32 Error] Unit2.pas(134): E2250 There is no overloaded version of >'RegisterJSONMarshalled' that can be called with these arguments
How do I fix this?
You can not change the attribute, but you can overwrite it.
According to the documentation, it should work with:
Marshaler.RegisterJSONMarshalled(TYourClass, 'Field1', true);
Therefore, you can not use the class function TJson.ObjectToJsonObject(...), - you'll have to create the marshaller (from the unit REST.JsonReflect) yourself. Example:
var
Marshaler: TJSONMarshal;
JSONObject: TJSOnObject;
begin
Marshaler := TJSONMarshal.Create(TJSONConverter.Create);
try
Marshaler.RegisterJSONMarshalled(TYourClass, 'Field1');
Marshaler.DateFormat :=jdfUnix;
JSONObject := Marshaler.Marshal(AObject) as TJSOnObject;
Result := JSONObject;
finally
FreeAndNil(Marshaler);
end;
end;
To remove the overwritten value you can call UnregisterJSONMarshalled.
Update to clarify how to this method is called:
The declared method signature is:
RegisterJSONMarshalled(clazz: TClass; Field: string; Marshal: Boolean);
So there are three parameters to pass in:
Marshaler.RegisterJSONMarshalled(myclass.ClassType, Field, Marshal);
or even more simpler:
Marshaler.RegisterJSONMarshalled(TMyClass, Field, Marshal);
You have to pass in the class type of your class.
No you cannot change attributes at runtime. You'll have to find a different approach to handle the dynamic nature of your marshaling.
Like the titles says, if I have multiple constructors (which is not good practice), which of them gets called?
Where did you hear that it was bad practice?
You can only have one constructor with a certain parameter list order so the one that gets called is the one specified by the argument you've passed in.
public class Test {
public Test() {...}
public Test(String str, int x) {...}
}
Say later on you call:
Test test = new Test("Hello", 46);
The constructor that gets called is this one:
public Test(String str, int x) {...}
Where the String "Hello" and the number 46 are passed in str and x respectively.
What programming language?
Everything depends on the language.
For Delphi, for example, it is valid to have to have multiple constructors. Even with the same name and with different parameters (both in quantity and type). In this case builders should be overloaded.
constructor TMyClass.Create overrride;
begin
// Code here
end;
constructor TMyClass.Create(Value: integer); override;
begin
// Code here
end;
constructor TMyClass.Create(Value1: integer; Value2: string); override;
begin
// Code here
end;
The compiler on their own know which one is going to run.
Indeed, it is even legal to do this:
constructor TMyClass.Create(Value3: double);
begin
Create(Random (9), 'Hello');
end;
Ie invoke one another.
The magic of the compiler decides when and what has to execute. And I have understood that this is in all LOO. ;)
I'm having trouble deserializing an object containing a interface field from json using SuperObject (serialization works fine) on DXE2. Consider the following:
ITest = interface(IInterface)
['{9E5623FF-1BC9-4FFA-919D-80C45EE24F38}']
function GetField3() : string;
procedure SetField3(Value: string);
property FField3: string read GetField3 write SetField3;
end;
TTest = class(TInterfacedObject, ITest)
private
FField3: string;
function GetField3() : string;
procedure SetField3(Value: string);
public
property Field3: string read GetField3 write SetField3;
constructor Create(Field3: string);
end;
TMyClass = class(TObject)
public
FField1: string;
FField2: string;
FTest: ITest;
constructor Create(Field1: string; Field2: string; Test: ITest);
end;
// TTest-stuff omitted for brevity.
constructor TMyClass.Create(Field1, Field2: string; Test: ITest);
begin
FField1 := Field1;
FField2 := Field2;
FTest := Test;
end;
var
MyClass: TMyClass;
MyClass2: TMyClass;
JSONObj: ISuperObject;
SuperContext: TSuperRttiContext;
begin
MyClass := TMyClass.Create('Test1', 'Test2', TTest.Create('Test3'));
SuperContext := TSuperRttiContext.Create();
JSONObj := SuperContext.AsJson<TMyClass>(MyClass);
WriteLn(JSONObj.AsString);
MyClass2 := SuperContext.AsType<TMyClass>(JSONObj);
MyClass2.Free();
ReadLn;
end.
When execution gets to TSuperRttiContext.FromJson.FromClass checking the FTest-field, the doo-doo hits the propeller in the ceiling (or table mounted, if you prefer that). At this point, Result := FromJson(f.FieldType.Handle, GetFieldDefault(f, obj.AsObject[GetFieldName(f)]), v); is called, which leads us into the interesting part of the SuperObject.pas code. I'll duplicated it here for brevity.
procedure FromInterface;
const soguid: TGuid = '{4B86A9E3-E094-4E5A-954A-69048B7B6327}';
var
o: ISuperObject;
begin
if CompareMem(#(GetTypeData(TypeInfo).Guid), #soguid, SizeOf(TGUID)) then
begin
if obj <> nil then
TValue.Make(#obj, TypeInfo, Value) else
begin
o := TSuperObject.Create(stNull);
TValue.Make(#o, TypeInfo, Value);
end;
Result := True;
end else
Result := False;
end;
The value assigned to soguid is that of ISuperObject, so clearly the two won't match (I'm testing for ITest, remember?). And so I'm a little lost of what to make of this. Is it illegal to deserialize any object composed of one or more interface fields?
This seems like such a common use case, that I find it hard to believe. I can appreciate the fact that knowing what implementation of a given interface to choose may be non-trivial. Yet, I see from the comment in the preamble, that interfaced objects are supposed to be supported - http://code.google.com/p/superobject/source/browse/trunk/superobject.pas#47.
Sure would be great if anyone have solved this out there. Thanks! :)
I have the following code that serializes a dynamic array of classes. For some reason SuperObject serializes on the private variables instead of the class property names. Can anyone please advise how to fix this behaviour in SuperObject?
class function TJSON.AsJSON<T>(AObject: T; Indent: Boolean = False): string;
var
Ctx: TSuperRttiContext;
begin
Ctx := TSuperRttiContext.Create;
try
Result := Ctx.AsJson<T>(AObject).AsJSon(Indent);
finally
Ctx.Free;
end;
end;
type
TMyClass = class
private
FName_: String;
FAge_: Integer;
public
property Name: String read FName_ write FName_;
property Age: Integer read FAge_ write FAge_;
end;
procedure TFormTest.Button27Click(Sender: TObject);
var
MyClassArray: TArray<TMyClass>;
MyClass1, MyClass2: TMyClass;
begin
MyClass1 := TMyClass.Create;
MyClass1.Name := 'Joe';
MyClass1.Age := 10;
MyClass2 := TMyClass.Create;
MyClass2.Name := 'Dan';
MyClass2.Age := 13;
SetLength(MyClassArray, 2);
MyClassArray[0] := MyClass1;
MyClassArray[1] := MyClass2;
Memo1.Text := TJSON.AsJSON<TArray<TMyClass>>(MyClassArray);
end;
The above code generates the following JSON:
[{"FName_":"Joe","FAge_":10},{"FName_":"Dan","FAge_":13}]
what I am after is the following JSON:
[{"Name":"Joe","Age":10},{"Name":"Dan","Age":13}]
I think it's not possible at this time and that you probably hit this issue. Even Delphi XE2 Datasnap serializes private fields at JSON marshalling and in my view it's just a consequence of a deeper visibility given to the new extended RTTI without considering the limits.
As far as I know, RTTI operates only on published properties (I may be wrong), but I think that you should simply switch your properties access level to published to get the desired JSON string.