Delphi - Serialization of objects : ignore inherited properties - json

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

Related

How to dynamically ignore property when I'm Serialization object to json using TJson.ObjectToJsonString

I have this class:
unit untPerson;
interface
type TPerson = class
private
fName : string;
fEmail : string;
fAge : integer;
published
property Name : string read fName write fName;
property Email : string read fEmail write fEmail;
property Age : integer read fAge write fAge;
end;
implementation
end.
And i need to serialize to Json using this code
TJson.ObjectToJsonString(objPerson, []);
But i need to skip Age if equal 0.
if objPerson.Age = 0 then
result := '{"name":"Lucas", "email":"lucas#github.com"}'
else
result := '{"name":"Lucas", "email":"lucas#github.com", "age":30}';
How Can I Do?
if objPerson.Age = 0 then
result := '{"name":"Lucas", "email":"lucas#github.com"}'
else
result := '{"name":"Lucas", "email":"lucas#github.com", "age":30}';
You can't dynamically ignore properties by using code: TJson.ObjectToJsonString(objPerson, []);.
You can put empty string by custom TJSONInterceptor, like Remy Lebeau says in comments(https://en.delphipraxis.net/topic/6155-tjson-suppressemptyvalues-for-empty-integer-double-and-class-fields/), but to exclude it from json you need to put option joIgnoreEmptyStrings: TJson.ObjectToJsonString(objPerson, [joIgnoreEmptyStrings]);.
One more way to do it: use custom converter (descendant of TJsonConverter), but problem is that you can’t use TJson.ObjectToJsonString in this case, because it have precompiled code with creating JSONMarshal using specifically TJSONConverter class without possibility of overriding: TJSONMarshal.Create(TJSONConverter.Create, true, CFRegConverters);. So you need to reimplement all chain of call TJSON.ObjectToJsonString -> ObjectToJsonValue -> TJSONConverters.GetJSONMarshaler -> TJSONMarshal.Create(TJSONConverter.Create, true, CFRegConverters);. And after that you must use this custom implementation.
Easiest way – to add custom method directly to class:
TPerson = class
private
fName : string;
fEmail : string;
fAge : integer;
published
function ToJsonString : string; virtual;
class function FromJsonString(const AJsonStr : string) : TPerson;
property Name : string read fName write fName;
property Email : string read fEmail write fEmail;
property Age : integer read fAge write fAge;
end;
In this case – you can use any custom logic, but the same code: TJson.ObjectToJsonString(objPerson, []);.will does not work correctly. You must use these new methods.
And last – you can try to find some other 3rd party JSON serializers.
As an alternative solution, mORMot have this diamond, and you can always use ObjectToJson to serialize very fast any TObject in a centralized way:
program TestJson;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Syncommons, mORMot;
type TPerson = class
private
fName : string;
fEmail : string;
fAge : integer;
public
class procedure ClassWriter(const aSerializer: TJSONSerializer;
aValue: TObject; aOptions: TTextWriterWriteObjectOptions);
published
property Name : string read fName write fName;
property Email : string read fEmail write fEmail;
property Age : integer read fAge write fAge;
end;
{ TPerson }
class procedure TPerson.ClassWriter(const aSerializer: TJSONSerializer;
aValue: TObject; aOptions: TTextWriterWriteObjectOptions);
var Person: TPerson absolute aValue;
begin
if Person.Age=0 then
aSerializer.AddJSONEscape(['Name',Person.Name,
'Email',Person.Email
])
else
aSerializer.AddJSONEscape(['Name',Person.Name,
'Email',Person.Email,
'Age',Person.Age
]);
end;
var Person : TPerson;
begin
TJSONSerializer.RegisterCustomSerializer(TPerson,nil,TPerson.ClassWriter);
Person := TPerson.Create;
try
Person.Name := 'Jon';
Person.Email := 'jon#gmail.com';
Person.Age := 10;
writeln(ObjectToJson(Person)); // Result {"Name":"Jon","Email":"jon#gmail.com","Age":10}
Person.Age := 0;
writeln(ObjectToJson(Person)); // Result {"Name":"Jon","Email":"jon#gmail.com"}
finally
Person.Free;
end;
readln;
end.
Please, find further details in the amazing documentation

Delphi REST.JSON TJson.JsonToObject Problem with inheritance

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.

Serialize and deserialize a collection to JSON Delphi

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.

How do I deserialize JSON into a List<SomeType> with Kotlin + Jackson [duplicate]

This question already has answers here:
How to use jackson to deserialize to Kotlin collections
(3 answers)
Closed 7 years ago.
What is the correct syntax to deserialize the following JSON:
[ {
"id" : "1",
"name" : "Blues"
}, {
"id" : "0",
"name" : "Rock"
} ]
I tried:
//Works OK
val dtos = mapper.readValue(json, List::class.java)
However I want:
val dtos : List<GenreDTO> = mapper.readValue(json,
List<GenreDTO>::class.java)
The above syntax is not correct and gives: only classes are allowed on the left hand side of a class literal
NOTE: The answer from #IRus is also correct, it was being modified at the same time I wrote this to fill in more details.
You should use the Jackson + Kotlin module or you will have other problems deserializing into Kotlin objects when you do no have a default constructor.
Your first sample of the code:
val dtos = mapper.readValue(json, List::class.java)
Is returning an inferred type of List<*> since you did not specify more type information, and it is actually a List<Map<String,Any>> which is not really "working OK" but is not producing any errors. It is unsafe, not typed.
The second code should be:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
val mapper = jacksonObjectMapper()
// ...
val genres: List<GenreDTO> = mapper.readValue(json)
You do not need anything else on the right side of the assignment, the Kotlin module for Jackson will reify the generics and create the TypeReference for Jackson internally. Notice the readValue import, you need that or .* for the com.fasterxml.jackson.module.kotlin package to have the extension functions that do all of the magic.
A slightly different alternative that also works:
val genres = mapper.readValue<List<GenreDTO>>(json)
There is no reason to NOT use the extension functions and the add-on module for Jackson. It is small and solves other issues that would require you to jump through hoops to make a default constructor, or use a bunch of annotations. With the module, your class can be normal Kotlin (optional to be data class):
class GenreDTO(val id: Int, val name: String)
The error you're getting is about following expression:
List<GenreDTO>::class.java
Because of how jvm treats generics there's no separate class for List<GenreDTO> thus compiler complains. Similarly in Java the following will not compile:
List<GenreDTO>.getClass()
Here's a sample that will deserialize the list properly:
val value:List<GenreDTO> = mapper.readValue(json, object : TypeReference<List<GenreDTO>>() {})
As #JaysonMinard has pointed out you can use jackson-module-kotlin to simplify the invocation to:
val genres: List<GenreDTO> = mapper.readValue(json)
// or
val genres = mapper.readValue<List<GenreDTO>>(json)
This is possible because of reified type parameters. Consider looking at Extensions to find out details.
Following code works well for me:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
val json = """[ {
"id" : "1",
"name" : "Blues"
}, {
"id" : "0",
"name" : "Rock"
} ]"""
data class GenreDTO(val id: Int, val name: String)
val mapper = ObjectMapper().registerKotlinModule()
fun main(args: Array<String>) {
val obj: List<GenreDTO> = mapper.readValue(json)
obj.forEach {
println(it)
}
}
This work because of extension function defined inside jackson-kotlin-module (that used reified generics):
public inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
Thanks #JaysonMinard for notify me about it.
Output:
GenreDTO(id=1, name=Blues)
GenreDTO(id=0, name=Rock)

SuperObject Serializes Private Variables instead of Properties

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.