Serialize and deserialize a collection to JSON Delphi - json

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.

Related

Delphi - Serialization of objects : ignore inherited properties

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

Default values for unknown elements in mORMot / Synopse JSON

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.

Retrofit/Spring Parsing Data Class to JSON Object

I have the following data class:
data class Thing(
val id: Long?,
val title: String,
val description: String,
)
In my Api :
#POST("doThings")
fun createThings(
#Query("thing") thing: Thing
): Call<StatusResponse>
I got the error:
status":500,"error":"Internal Server Error","message":"Unexpected character ('E' (code 69))
In the spring api I made a log output and the data class object arrived as:
"Thing(id=null, title=Something, description=Something more)"
The Retrofit Builder has the GSON Converter but I guess it doesn't work properly:
Retrofit.Builder()
.client(get())
.baseUrl(get<Context>().getString(R.string.base_url))
.addCallAdapterFactory(get<CoroutineCallAdapterFactory>())
.addConverterFactory(get<GsonConverterFactory>())
.build()
Any suggestions? Thanks
You are using the #Query annotation which means your Thing will be serialised as a String and passed as a query parameter in the URL.
You instead want to use the #Body annotation which will serialise the Thing object as JSON and add it to the POST body.
This answer will give you more details on how to use that annotation: https://stackoverflow.com/a/21423093/5577048

Jackson with ScalaModule. Deserialize case class with auxiliary constructor

Basically I want null arrays to be deserialized as empty collection by Jackson with scala module. As I understand this is not implemented right now so I'm looking for a workaround. I think it could work if I have auxiliary constructor with Option[Seq[Any]] parameter. Like this:
case class Test (id: Int, seq: Seq[Any]){
#JsonCreator
def this(id: Int, seq: Option[Seq[Any]]) = this(id, seq.getOrElse(Seq()))
}
But it doesn't work since Jackson gives me this exception:
Caused by: java.lang.IllegalArgumentException: Conflicting
property-based creators: already had explicitly marked [constructor
for com.test.Main$Test, annotations: [null]], encountered [constructor
for com.test.Main$Test, annotations: {interface
com.fasterxml.jackson.annotation.JsonCreator=#com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]
It's interesting that if I remove id field it starts working:
case class Test (seq: Seq[Any]){
#JsonCreator
def this(seq: Option[Seq[Any]]) = this(seq.getOrElse(Seq()))
}
Could somebody explain me what is going on here?
Can I have overloaded constructor as json creator with case classes?
Maybe you see another workaround to my initial problem(deserializing null as empty collection)?
p.s. about my initial problem. I don't want to have Option[Seq[Any]] and I don't want to have getter because my constructor param will have ugly name like _seq and in scala parameter names is also a public API.

deserialize lazylist using jackson

I have a object which uses a org.apache.commons.collections.list.LazyList for one of its fields, which is serialized ti JSON. The JSON looks like this:
"myObject": ["org.apache.commons.collections.list.LazyList", [
{
"attr1": "asdasd",
"attr2": 1234
}
]],
The object field looks like this:
List<MyObject> myObject = ListUtils.lazyList(new ArrayList(), {new MyObject()} as Factory)
However trying to deserialize the above JSON using a Jackson ObjectMapper fails, since it can't find a default constructor for a LazyList - which makes sense. But how can I specify how this field can be deserialized?
Error message:
No default constructor for [collection type; class org.apache.commons.collections.list.LazyList, contains [simple type, class foo.bar.MyObject]]
Bounty-constraints:
To collect the bounty, this question needs to be answered using a custom jackson deserializer - the custom deserializer must not be field specific! Hence no solution using custom implementations of a LazyList for a specific type will answer this question adequately.
The solution below worked on both List and Map collection objects, it might also work on yours.
#JsonDeserialize(contentAs=MyObject.class)
private List<MyObject> myObject = ListUtils.lazyList(new ArrayList(), {new MyObject()} as Factory);