I try serialize/deserialize standard delphi container using standard delphi serializer.
procedure TForm7.TestButtonClick(Sender: TObject);
var
dict: TDictionary<Integer, Integer>;
jsonValue: TJSONValue;
begin
//serialization
dict := TDictionary<Integer, Integer>.Create;
dict.Add(1, 1);
jsonValue := TJsonConverter.ObjectToJSON(dict);
dict.Free;
//deserialization
dict := TJsonConverter.JSONToObject(jsonValue) as TDictionary<Integer, Integer>;
try
Assert(dict.ContainsKey(1), 'deserialization error - key not found');
except
Assert(false, 'deserialization error - dict object broken');
end;
end;
There is a way I convert object to JSON and vice versa;
class function TJsonConverter.JSONToObject(AJSONValue: TJSONValue): TObject;
var
lUnMarshal: TJSONUnMarshal;
begin
lUnMarshal := TJSONUnMarshal.Create();
try
Result := lUnMarshal.Unmarshal(AJSONValue);
finally
lUnMarshal.Free;
end;
end;
class function TJsonConverter.ObjectToJSON(AData: TObject): TJSONValue;
var
lMarshal: TJSONMarshal;
begin
lMarshal := TJSONMarshal.Create();
try
Result := lMarshal.Marshal(AData);
finally
lMarshal.Free;
end;
end;
line:
dict := TJsonConverter.JSONToObject(jsonValue) as TDictionary<Integer, Integer>;
doesn't create dictionary correctly. Here is how looks dict create by constructor: [
and here is dict created by deserialization:
How can I fix it?
Edit: Here is JSON content
{
"type" : "System.Generics.Collections.TDictionary<System.Integer,System.Integer>",
"id" : 1,
"fields" : {
"FItems" : [
[ -1, 0, 0 ],
[ -1, 0, 0 ],
[ -1, 0, 0 ],
[ 911574339, 1, 1 ]
],
"FCount" : 1,
"FGrowThreshold" : 3,
"FKeyCollection" : null,
"FValueCollection" : null
}
}
The problem is that TJSONMarshal
is instantiating the dictionary using RTTI. It does that by invoking the first parameterless constructor that it can find. And, sadly, that is the the constructor defined in TObject
.
Let's take a look at the constructors declared in TDictionary<K,V>
. They are, at least in my XE7 version:
constructor Create(ACapacity: Integer = 0); overload;
constructor Create(const AComparer: IEqualityComparer<TKey>); overload;
constructor Create(ACapacity: Integer; const AComparer: IEqualityComparer<TKey>); overload;
constructor Create(const Collection: TEnumerable<TPair<TKey,TValue>>); overload;
constructor Create(const Collection: TEnumerable<TPair<TKey,TValue>>;
const AComparer: IEqualityComparer<TKey>); overload;
All of these constructors have parameters.
Don't be fooled by the fact that you write
TDictionary<Integer, Integer>.Create
and create an instance with FComparer
assigned. That resolves to the first overload above and so the compiler re-writes that code as
TDictionary<Integer, Integer>.Create(0)
filling in the default parameter.
What you need to do is make sure that you only use classes that have parameterless constructors that properly instantiate the class. Unfortunately TDictionary<K,V>
does not fit the bill.
You can however derive a sub-class that introduces a parameterless constructor, and your code should work with that class.
The following code demonstrates:
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Generics.Collections,
System.Rtti;
type
TDictionary<K,V> = class(System.Generics.Collections.TDictionary<K,V>)
public
constructor Create;
end;
{ TDictionary<K, V> }
constructor TDictionary<K, V>.Create;
begin
inherited Create(0);
end;
type
TInstance<T: class> = class
class function Create: T; static;
end;
class function TInstance<T>.Create: T;
// mimic the way that your JSON marshalling code instantiates objects
var
ctx: TRttiContext;
typ: TRttiType;
mtd: TRttiMethod;
cls: TClass;
begin
typ := ctx.GetType(TypeInfo(T));
for mtd in typ.GetMethods do begin
if mtd.HasExtendedInfo and mtd.IsConstructor then
begin
if Length(mtd.GetParameters) = 0 then
begin
cls := typ.AsInstance.MetaclassType;
Result := mtd.Invoke(cls, []).AsType<T>;
exit;
end;
end;
end;
Result := nil;
end;
var
Dict: TDictionary<Integer, Integer>;
begin
Dict := TInstance<TDictionary<Integer, Integer>>.Create;
Dict.Add(0, 0);
Writeln(BoolToStr(Dict.ContainsKey(0), True));
Readln;
end.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With