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.
Related
I'm trying to learn how to serialize objects with Delphi 10.3.3 Rio using this units : REST.JsonReflect and REST.JSON.Types
I wrote this class :
TContainer: class(TGraphicControl)
private
[JSONMarshalledAttribute(False)] // Test to hide a property : works
FObj: TObject;
FId: Integer;
FContainerName: string;
[JsonName('Type')] // Test to customize the property name : works
FContainerType: string;
public
property Id: Integer read FId write FId;
property ContainerName: string read FContainerName write FContainerName;
property ContainerType: string read FContainerType write FContainerType;
end;
To serialize i do like that :
var
m: TJSONMarshal;
JSONString: String;
Container: TContainer;
begin
Container:= TContainer.Create(nil);
Container.FId := 12;
Container.FContainerName := 'Container001';
Container.FContainerType := 'TYPE_005';
JSONString := m.Marshal(Container).Format;
So my code is working when my class doesn't inherit from any another class, if i declare it like this :
TContainer: class
But when i do like above, with :
TContainer: class(TGraphicControl)
I have a stack overflow exception. I think it's due to inheritance.
So my question is, is it possible to skip inherits properties in order that after the serialization, JSONString look like :
{
"id": 7,
"containerName": "Container001",
"Type": "TYPE_005"
}
Thanks
[EDIT] :
I find this, with the library Newtonsoft.Json in .NET, a stack overflow user suggest to add this annotation :
[JsonObject(MemberSerialization.OptIn)].
"In a sentence, it will serialize only properties decorated with [JsonProperty] attribute."
But i can't find anything like this with Delphi
I really love the way mORMot / Synopse has implemented the handling of JSON, especially the way you can use the JSON elements in your code (like MyString := myjson.name). This is very intuitive and useful in wrapping objects which only have one Variant (JSON) with state which we access through getters/setters, like below:
TMyObject = class
private
FState: Variant;
function GetName: String;
public
constructor Create(AJson: Variant);
property Name: String read GetName;
end;
function TMyObject.GetName: String;
begin
Result := FState.name;
end;
It is really powerful, but I would like to get the 'default' variant value if an element is not found in the corresponding JSON document (so, above example should return an empty string if 'name' is not present).
I don't want to use NullStrictConvert, because that is not thread safe and influences the rest of our program. Of course I could check VarIsNull(FState.name), but then I have to do this for each element and I prefer not to have this extra boilerplate.
Any suggestions?
When you unserialize some JSON into an object, the missing fields are left untouched IIRC.
So you can just set the fields to their default values before unserializing the JSON.
One way of doing it is to inherit from TSynPersistent and override the Create constructor and set default value(s).
Edit: You may use a TDocVariantData instead of variant, and call GetAsRawUTF8() and such methods which return false if the property is not existing.
I've searched a lot, but cannot find a working solution. Most likely because I'm pretty new to Delphi (and coming from C#).
I have a unit with the following class declaration:
TReplaceField = class
public
Key: string;
Value: string;
constructor Create; overload;
constructor Create(strKey, strValue: string); overload;
end;
Class can also be record, like suggested in the comment.
I want to create a collection of it, be it an array, TList<T>, or whatever suits best in this case, and then serialize it to JSON (to store it) and later deserialze it to loop over the fields.
I've found REST.JSON and TJson.ObjectToJsonString(), which works good for 1 object.
But if I pass an array of TReplaceField, it gives me a compile error:
E2010 Incompatible types: 'TObject' and 'Dynamic array'
When I use a TList at runtime, I get a different error:
type tkPointer is not currently supported
In C#, all of this is pretty easy, like a few lines of code. I'm pretty stuck on how to do it in Delphi (besides creating JSON by hand via string concatenation).
UPDATE:
With the tips from David, I've got the serialization working. Also deserialization of one object works, but not deserialization of a collection.
My class now looks like this:
TReplaceField = class
private
FKey : string;
FValue : string;
published
property Key : string read FKey write FKey;
property Value : string read FValue write FValue;
public
constructor Create; overload;
constructor Create(strKey, strValue: string); overload;
end;
Serialize one object: mmoJson.Lines.Text := TJson.ObjectToJsonString(Field); OK
Deserialize one object: JsonToObject<TReplaceField>(mmoJson.Lines.Text); OK
Serialize collection: mmoJson.Lines.Text := TJson.ObjectToJsonString(FieldList); OK
Deserialize collection: JsonToObject<TReplaceField>(mmoJson.Lines.Text); FAIL, message:
EConversionError with message 'Internal: Cannot instantiate type System.Generics.Collections.TList<uReplaceField.TReplaceFieldRec>'
Using a record instead of class for ReplaceField gives the same results.
Almost there. What am I missing to get it fully working?
type
TReplaceCollect = TList<TReplaceField>;
Add this after-class declaration.
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.
I have a object with some TObjectList<>-fields that I try to encode as JSON with help form SuperObject.
TLogs = TObjectList<TLog>;
TMyObject = class(TObject)
private
FLogs: TLogs;
end;
Deep inside SuperObjects code, there is a ToClass procedure, iterating the fields and add them to the json result.
In this loop, there is a check on the TRttiFields FieldType. If it's nil, it skips the object.
for f in Context.GetType(Value.AsObject.ClassType).GetFields do
if f.FieldType <> nil then
begin
v := f.GetValue(value.AsObject);
result.AsObject[GetFieldName(f)] := ToJson(v, index);
end
My generic list fields have a FieldType of nil. Why?
How can I make SuperObject serialize my list of objects?
This is a known issue in Delphi's RTTI creation. If you declare your generic class like that, it won't work. You need to use the class keyword.
TLogs = class(TObjectList<TLog>);
Hopefully this will be fixed in the next release.