paolo-rossi / delphi-neon Goto Github PK
View Code? Open in Web Editor NEWJSON Serialization library for Delphi
License: Apache License 2.0
JSON Serialization library for Delphi
License: Apache License 2.0
function TNeonSerializerJSON.WriteDate(const AValue: TValue; ANeonObject: TNeonRttiObject): TJSONValue;
begin
case ANeonObject.NeonInclude.Value of
IncludeIf.NotEmpty, IncludeIf.NotDefault:
begin
if AValue.AsExtended = 0 then
Exit(nil);
end;
end;
if AValue.AsType <> 0 then
Result := TJSONString.Create(TJSONUtils.DateToJSON(AValue.AsType, FConfig.UseUTCDate))
else
Result := TJSONNull.Create;
end;
I've been having a problem with deserializing a class I have. It seems to destroy the property when it should not.
The code is in line 1247 (method TNeonDeserializerJSON.ReadMembers) and the 2 lines are
LMemberValue := ReadDataMember(LParam, LNeonMember.GetValue);
LNeonMember.SetValue(LMemberValue);
if I have a class with a property like
property MyProp: TObject read GetMyProp write SetMyProp;
and the SetMyProp looks like this
Procedure SomeObject.SetMyProp(Value: TObject);
begin
if Assigned(FMyProp) then
FMyProp.Free;
FMyProp := Value;
end;
then the 2 mentioned lines perform as follows.
The 1st line gets a copy of the object into LMemberValue
The second line then tries to set the value to the same value, but this ends up disposing of the value.
I suspect this is possibly a technicalality of the code and may not really be a bug, but might be something that cannot easily be remedied. Perhaps a new attribute could solve it. In the mean time I'll try to disable my free in the property setter until after the deserializing has completed.
Another solution would be not to free it in the setter but have the caller free it before setting it, but that doesn't quite feel right.
Any thoughts ?
Unless I've missed it, it would be nice for TNeon
to have a ValueToJSONString method. Currently I'm using this helper method:
class function TNeonHelper.ValueToJSONString(const AValue: TValue): string;
var
LJSON: TJSONValue;
begin
LJSON := ValueToJSON(AValue);
try
Result := LJSON.ToString;
finally
LJSON.Free;
end;
end;
Saves having to manage the TJSONValue yourself.. and the reverse:
class function TNeonHelper.JSONStringToValue<T>(const AJSON: string): T;
begin
Result := JSONStringToValue<T>(AJSON, nil);
end;
class function TNeonHelper.JSONStringToValue<T>(const AJSON: string; const AConfig: INeonConfiguration): T;
var
LJSON: TJSONValue;
begin
LJSON := TJSONObject.ParseJSONValue(AJSON);
if LJSON = nil then
LJSON := TJSONObject.Create;
try
if AConfig = nil then
Result := JSONToValue<T>(LJSON)
else
Result := JSONToValue<T>(LJSON, AConfig);
finally
LJSON.Free;
end;
end;
Hi,
Our team wants to use Neon library for complex type with sub-objects. When the sub-objects are created in constructor everything works prefectly, but we want to have them unassigned unless they are provided in json.
For example :
We have a class
TPerson = class
private
Fname: string;
FSurname: string;
FNote: TNote;
published
property Name: string read Fname write Fname;
[NeonProperty('LastName')]
property Surname: string read FSurname write FSurname;
property Note: TNote read FNote write FNote;
end;
When we want to deserialize json:
{
"Name":"Paolo",
"LastName":"Rossi",
"Note":{
"Text":"ADFGH"
}
}
the Note property is skipped and remains nil.
Is it possible to assign the Note property without creating it first in TPerson's constructor?
The two tests introduced in cd39227 are missing the files they try to load with TTestUtils.ExpectedFromFile(..)
. The tests will fail by throwing an unexpected exception because they don't find the files.
Hello Paolo
I have updated Delphi-Neon recently and I found different behavior comparing to the version before. I use deserialization from JSON and classes with Nullable<> types.
In short, the problem is with deserializing an empty string from JSON to Nullable<> type. It is deserialized with flag HasValue = false, but should be true.
Here's a simple example that shows the behavior and what's wrong with that. There are 4 test methods. Look for '//FAIL' comment in runTest2
below:
program Example1Proj;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Generics.Collections,
System.TypInfo,
System.Json,
Neon.Core.Types,
Neon.Core.Attributes,
Neon.Core.Nullables,
Neon.Core.Persistence,
Neon.Core.Persistence.JSON;
type
TExampleEnumNames = class
public const
cEMPTY = '';
cENUM1 = 'ENUM1';
EnumNames = cEMPTY + ',' + cENUM1;
end;
[NeonEnumNames(TExampleEnumNames.Enumnames)]
TExampleEnum = (
evEMPTY,
evENUM1
);
TExampleClass = class
private
FExampleEnum: Nullable<TExampleEnum>;
FExampleString: Nullable<string>;
public
[NeonInclude(IncludeIf.NotNull)]
property exampleEnum: Nullable<TExampleEnum> read FExampleEnum write FExampleEnum;
[NeonInclude(IncludeIf.NotNull)]
property exampleString: Nullable<string> read FExampleString write FExampleString;
end;
function getNeonConfig: INeonConfiguration;
begin
result := TNeonConfiguration.Default;
result.SetMemberCase(TNeonCase.Unchanged);
result.SetMembers([TNeonMembers.Fields, TNeonMembers.Properties]);
result.SetIgnoreFieldPrefix(True);
result.SetVisibility([mvPublic, mvPublished]);
result.SetUseUTCDate(false);
result.SetEnumAsInt(false);
end;
procedure DeserializeObject(AObject: TObject; const AJson: string);
var
LJSON: TJSONValue;
lConfig: INeonConfiguration;
LReader: TNeonDeserializerJSON;
begin
lConfig := getNeonConfig;
LJSON := TJSONObject.ParseJSONValue(AJson);
if not Assigned(LJSON) then
raise ENeonException.Create('Unable to parse json');
try
LReader := TNeonDeserializerJSON.Create(lConfig);
try
LReader.JSONToObject(AObject, LJSON);
if LReader.Errors.Count > 0 then
raise ENeonException.Create(LReader.Errors.Text);
finally
LReader.Free;
end;
finally
LJSON.Free;
end;
end;
procedure runTest1;
var
testObj: TExampleClass;
begin
testObj := TExampleClass.Create;
DeserializeObject(testObj, '{ "exampleString": "ABC", "exampleEnum": "ENUM1" }');
assert(testObj <> nil); //OK
assert(testObj.exampleString.HasValue = true); //OK
assert(testObj.exampleString.Equals('ABC')); //OK
assert(testObj.exampleEnum.HasValue = true); //OK
assert(testObj.exampleEnum = evENUM1); //OK
end;
procedure runTest2;
var
testObj: TExampleClass;
begin
testObj := TExampleClass.Create;
DeserializeObject(testObj, '{ "exampleString": "", "exampleEnum": "" }');
{ Below there are two fails that I consider as invalid behavior }
assert(testObj <> nil); //OK
assert(testObj.exampleString.HasValue = true); //FAIL
assert(testObj.exampleString.Equals('')); //OK
assert(testObj.exampleEnum.HasValue = true); //FAIL
assert(testObj.exampleEnum = evEMPTY); //OK
end;
procedure runTest3;
var
testObj: TExampleClass;
z: string;
begin
testObj := TExampleClass.Create;
DeserializeObject(testObj, '{ "exampleString": null, "exampleEnum": null }');
assert(testObj <> nil); //OK
assert(testObj.exampleString.HasValue = false); //OK
//assert(testObj.exampleString.Equals('')); //OK - can't test this; it has no value
assert(testObj.exampleEnum.HasValue = false); //OK
//assert(testObj.exampleEnum = evEMPTY); //OK - cant't test this; it has no value
end;
procedure runTest4;
var
testObj: TExampleClass;
begin
testObj := TExampleClass.Create;
DeserializeObject(testObj, '{}');
assert(testObj <> nil); //OK
assert(testObj.exampleString.HasValue = false); //OK
//assert(testObj.exampleString.Equals('')); //OK - can't test this; it has no value
assert(testObj.exampleEnum.HasValue = false); //OK
//assert(testObj.exampleEnum = evEMPTY); //OK - cant't test this; it has no value
end;
begin
try
runTest1;
runTest2;
runTest3;
runTest4;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
In my opinion it works wrong because of the method in Neon.Core.Utils.pas:
class function TJSONUtils.IsNotEmpty(const AJSON: TJSONValue): Boolean;
There wasn't that method in previous version of Neon and it worked as expected. Now I have this issue. When the string is set and empty in JSON, it should be deserialized as: Value = '' and HasValue = 'True'
So I think that these 2 lines in mentioned method should be removed:
if AJSON is TJSONString then
Exit(not (AJSON as TJSONString).Value.IsEmpty);`
When the value is regarded as empty, there is no attempt to deserialize it and HasValue is not set.
Thank you
I have tested in my application and with your application to use a Nullable value. Your example is the TClassOfNullables with Nullable
It is not using the NeonEnumNames when used in this manner Nullable
Regards,
Jacques
Hello,
Please note this warning:
[dcc32 Warning] Neon.Core.Utils.pas(753): W1035 Return value of function 'TJSONUtils.GetValueBool' might be undefined
Best regards.
This Serializer would be used for Records.
type
Serializer =
record
class function Marshal<T>(const Data:T):string;inline;static;
class function Unmarshal<T>(const JsonText:string;out Data:T):Boolean;inline;static;
end;
implementation
class function Serializer.Marshal<T>(const Data:T):string;
begin
try
Result := TNeon.ValueToJSON(TValue.From(Data)).ToJSON;
except
Result := '{null}';
end;
end;
class function Serializer.Unmarshal<T>(const JsonText:string;out Data:T):Boolean;
var
AJSON : TJSONObject;
begin
AJSON := TJSONObject.Create;
Result := False;
try
AJSON.Parse(BytesOf(JsonText),0);
Data := TNeon.JSONToValue<T>(AJSON);
Result := True;
except
Data := default(T);
end;
FreeAndNil(AJSON);
end;
Best regards.
Using the following code:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure DynArrayTest;
procedure DynGridTest;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
uses
System.IOUtils, System.Rtti, System.JSON,
Neon.Core.Persistence, Neon.Core.Persistence.JSON;
type
TNeonHelper = class helper for TNeon
public
class function JSONStringToValue<T>(const AJSON: string): T; overload;
class function JSONStringToValue<T>(const AJSON: string; const AConfig: INeonConfiguration): T; overload;
end;
TRow = TArray<Int64>;
TGrid = TArray<TRow>;
TTest = record
Grid: TGrid;
constructor Create(const AWidth, AHeight: Integer);
end;
TTest2 = record
Row: TRow;
constructor Create(const AWidth: Integer);
end;
class function TNeonHelper.JSONStringToValue<T>(const AJSON: string): T;
begin
Result := JSONStringToValue<T>(AJSON, nil);
end;
class function TNeonHelper.JSONStringToValue<T>(const AJSON: string; const AConfig: INeonConfiguration): T;
var
LJSON: TJSONValue;
begin
LJSON := TJSONObject.ParseJSONValue(AJSON);
if LJSON = nil then
LJSON := TJSONObject.Create;
try
if AConfig = nil then
Result := JSONToValue<T>(LJSON)
else
Result := JSONToValue<T>(LJSON, AConfig);
finally
LJSON.Free;
end;
end;
{ TTest }
constructor TTest.Create(const AWidth, AHeight: Integer);
var
I: Integer;
begin
SetLength(Grid, AHeight);
for I := 0 to AHeight - 1 do
SetLength(Grid[I], AWidth);
end;
{ TTest2 }
constructor TTest2.Create(const AWidth: Integer);
begin
SetLength(Row, AWidth);
end;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
// DynArrayTest;
DynGridTest;
end;
procedure TForm1.DynArrayTest;
var
LTest2: TTest2;
LFileName: string;
begin
LFileName := TPath.Combine(TPath.GetTempPath, 'test2.json');
LTest2 := TTest2.Create(5);
TFile.WriteAllText(LFileName, TNeon.ValueToJSONString(TValue.From(LTest2)));
LTest2 := TNeon.JSONStringToValue<TTest2>(TFile.ReadAllText(LFileName));
end;
procedure TForm1.DynGridTest;
var
LTest: TTest;
LFileName: string;
begin
LFileName := TPath.Combine(TPath.GetTempPath, 'test.json');
LTest := TTest.Create(5, 7);
TFile.WriteAllText(LFileName, TNeon.ValueToJSONString(TValue.From(LTest)));
LTest := TNeon.JSONStringToValue<TTest>(TFile.ReadAllText(LFileName));
end;
end.
The DynGridTest method manages to serialize the JSON for LTest, however it fails to deserialize. The root cause is the TRttiUtils.CreateNewValue
method in Neon.Core.Utils
which does not handle tkDynArray, thus raises an exception.
In my opinion Delphi 2010 is too old :(
In your code I see "string.split" or "string.isEmpty" from System.SysUtils.TStringHelper
TNeonDeserializerJSON.ReadEnum should use TJSONBool instead of TJSONTrue and TJSONFalse.
type
TClassWithBool = class
private
FBoolProp: Boolean;
public
property BoolProp: Boolean read FBoolProp write FBoolProp;
end;
procedure TestBool;
var
LJSON: TJSONObject;
LReader: TNeonDeserializerJSON;
LClass: TClassWithBool;
begin
LClass := nil;
LReader := nil;
LJSON := TJSONObject.Create;
try
LJSON.AddPair('BoolProp', True);
LReader := TNeonDeserializerJSON.Create(TNeonConfiguration.Default);
LClass := TClassWithBool.Create;
LReader.JSONToObject(LClass, LJSON);
ShowMessage(LReader.Errors.Text); // => Error converting member [BoolProp] of type [TClassWithBool]: Invalid JSON value. Boolean expected
finally
LJSON.Free;
LClass.Free;
LReader.Free;
end;
end;
Suggested changes
function TNeonDeserializerJSON.ReadEnum(const AParam: TNeonDeserializerParam): TValue;
var
LIndex, LOrdinal: Integer;
LTypeData: PTypeData;
begin
if AParam.RttiType.Handle = System.TypeInfo(Boolean) then
begin
if AParam.JSONValue is TJSONBool then
Result := (AParam.JSONValue as TJSONBool).AsBoolean
else
raise ENeonException.Create('Invalid JSON value. Boolean expected');
end
[...]
Can I set the number of decimal places for persistence?
As far as I can see the source Source\Neon.Core.Persistence.Swagger.pas is missing.
Regards, Jan.
I can reproduce this exception with the newest version of neon-json with the code below.
program NeonBugTGUID;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils,system.json,
Neon.Core.Types,
Neon.Core.Attributes,
Neon.Core.Persistence,
Neon.Core.Persistence.JSON,
Neon.Core.Utils;
type
TMyData = class
private
public
ID: TGUID;
end;
var
JSONString: string;
MyData: TMyData;
JSONValue: TJSONValue;
config: INeonConfiguration;
begin
config:=TNeonConfiguration.Default;
config.SetMembers([TNeonMembers.Fields]);
try
JSONString := '{"ID": "B7A1A8D1-82D3-4D02-99C7-6A538B066FC4"}';
MyData := TMyData.Create;
JSONValue := TJSONObject.ParseJSONValue(JSONString);
try
TNeon.JSONToObject(MyData, JSONValue, config);
WriteLn('GUID aus JSON gelesen: ', GUIDToString(MyData.ID));
finally
JSONValue.Free;
end;
ReadLn;
finally
MyData.Free;
end;
end.
I am trying to deserialize the following json below, and the component is causing an error in the method.
function TNeonDeserializerJSON.ReadEnumerable(const AParam: TNeonDeserializerParam; const AData: TValue): Boolean;
This is because I have an array within an array, and the elements of the last array are merged between integers and string.
When the system tries to call ReadDataMember again passing LItemValue as an integer, it does not recognize and gives an error.
Would you help me ?
{ "iTotalRecords": 34, "iTotalDisplayRecords": 34, "aaData": [ [ 1, 1, 1, "Papel de 5 cm", "Sim", "Não", "Não", " " ], [ 2, 1, 1, "Papel de 6 cm", "Sim", "Não", "Não", " " ], [ 3, 1, 1, "Papel de 7 cm", "Sim", "Não", "Não", " " ], [ 4, 1, 1, "Papel de 8 cm", "Sim", "Não", "Não", " " ], [ 5, 1, 1, "Papel de 9 cm", "Sim", "Não", "Não", " " ], [ 6, 1, 1, "Papel de 10 cm", "Sim", "Não", "Não", " " ], [ 7, 1, 1, "Papel de 11 cm", "Sim", "Não", "Não", " " ], [ 8, 1, 1, "Papel de 12 cm", "Sim", "Não", "Não", " " ], [ 9, 1, 1, "Papel de 15 cm", "Sim", "Não", "Não", " " ], [ 10, 1, 1, "Papel de 17 cm", "Sim", "Não", "Não", " " ], [ 11, 1, 1, "Papel de 20 cm", "Sim", "Não", "Não", " " ], [ 12, 1, 1, "Papel de 25 cm", "Sim", "Não", "Não", " " ], [ 13, 1, 1, "Papel de 30 cm", "Sim", "Não", "Não", " " ], [ 14, 1, 1, "Papel de 35 cm", "Sim", "Não", "Não", " " ], [ 15, 1, 1, "Papel de 40 cm", "Sim", "Não", "Não", " " ], [ 16, 1, 1, "Papel de 45 cm", "Sim", "Não", "Não", " " ], [ 17, 1, 1, "Papel de 50 cm", "Sim", "Não", "Não", " " ], [ 18, 1, 1, "Papel de 60 cm", "Sim", "Não", "Não", " " ], [ 19, 1, 1, "Papel de 13 cm", "Sim", "Não", "Não", " " ], [ 20, 1, 1, "Envelope 12 x 10", "Sim", "Não", "Não", " " ], [ 21, 1, 1, "Envelope 10 x 18", "Sim", "Não", "Não", " " ], [ 22, 1, 1, "Envelope 12 x 15", "Sim", "Não", "Não", " " ], [ 23, 1, 1, "Envelope 12 x 28", "Sim", "Não", "Não", " " ], [ 24, 1, 1, "Envelope 15 x 17", "Sim", "Não", "Não", " " ], [ 25, 1, 1, "Envelope 15 x 29", "Sim", "Não", "Não", " " ], [ 26, 1, 1, "Envelope 20 x 24", "Sim", "Não", "Não", " " ], [ 27, 1, 1, "Envelope 20 x 40", "Sim", "Não", "Não", " " ], [ 28, 1, 1, "Envelope 25 x 27", "Sim", "Não", "Não", " " ], [ 29, 1, 1, "Envelope 25 x 35", "Sim", "Não", "Não", " " ], [ 30, 1, 1, "Envelope 30 x 36", "Sim", "Não", "Não", " " ], [ 32, 1, 1, "SMS PESADO 1200X1200", "Sim", "Não", "Não", " " ], [ 34, 1, 1, "PAPEL GRAU CIRURGICO", "Sim", "Sim", "Sim", " " ], [ 35, 1, 1, "SMS", "Sim", "Sim", "Não", " " ], [ 36, 1, 1, "Tecido", "Sim", "Sim", "Não", " " ] ] }
Please how to implement TCustomSerializer for TBytes?
The content of TBytes would be a container for a Binary Data, Encrepted Data, ... So marshaling TBytes is made by Base64 encoding/decoding.
TBytesSerializer = class(TCustomSerializer)
protected
class function GetTargetInfo:PTypeInfo;override;
class function CanHandle(AType:PTypeInfo):Boolean;override;
public
function Serialize(const AValue:TValue;ANeonObject:TNeonRttiObject;AContext:ISerializerContext):TJSONValue;override;
function Deserialize(AValue:TJSONValue;const AData:TValue;ANeonObject:TNeonRttiObject;AContext:IDeserializerContext):TValue;override;
end;
implementation
class function TBytesSerializer.GetTargetInfo: PTypeInfo;
begin
Result := TypeInfo(TBytes);
end;
class function TBytesSerializer.CanHandle(AType: PTypeInfo): Boolean;
begin
Result := (AType=GetTargetInfo);
end;
function TBytesSerializer.Serialize(const AValue:TValue;ANeonObject:TNeonRttiObject;AContext:ISerializerContext):TJSONValue;
var
LBytes : TBytes;
LBase64 : string;
begin
SetLength(LBytes,AValue.GetArrayLength);
if Length(LBytes)>0 then
begin
AValue.ExtractRawData(LBytes);
LBase64 := TBase64.Encode(LBytes);
end
else LBase64 := '';
Result := TJSONString.Create(LBase64);
end;
function TBytesSerializer.Deserialize(AValue:TJSONValue;const AData:TValue;ANeonObject:TNeonRttiObject;AContext:IDeserializerContext):TValue;
var
LBytes : TBytes;
begin
LBytes := TBase64.Decode(AValue.Value);
AData.Make(LBytes,GetTargetInfo,Result);
end;
Best regards.
The TTime is serialized as TDateTime.
12:15:00 is serialized as 1899-12-30T12:15:00.000Z instead of 12:15:00.
Best regards.
I have a problem converting this structure into swagger.json.
If I am calling the /swagger endpoint from WiRL the service crashes.
If I call the GET endpoint that returns this structure then it works fine.
TObjectA = class;
TObjectA = class
private
FNodes: TObjectDictionary<string, TObjectA>
public
property Nodes : TObjectDictionary<string, TObjectA> read FNodes write FNodes;
end;
How can I fix this problem?
Thanks
I am trying to use a set of ENUM e.g.
[NeonEnumNames('Low Speed,Medium Speed,High Speed')]
TEnumSpeed = (Low, Medium, High);
TEnumSpeedSet = set of TEnumSpeed
In this use case it does not use the NeonEnumNames.
I have then tried to change it to an array:
TEnumSpeedSet = Array of TEnumSpeed
It then creates it correctly, but when serializing back to object it fails?
Any suggestion
Hi, Great library. Does way more than all the others !
If I create a property like
property Prop: Nullable<TObject> read FProp write FProp;
and in the constructor have
FProp := TObject.Create;
if I assign nil to the property in my code like
var x: TMyData;
x := TMyData.Create; // constructor creates instance of TObject for FProp
x.Prop := nil;
x.Prop is not freed. I could free it manually, but in the TNeon.JSONToObject I don't get that opportunity and the object leaks. eg, Json string is
{}
ie there is no Prop in the JSON string. This creates the object correctly but rather than having a nil value for Prop, it has a TObject there and the HasValue property for Prop is true even though it had no value in the JSON.
I looked at the available attributes and couldn't find one that would solve this. Is this a bug or have I done something wrong ?
I think I can get around it by processing the JSON objects and removing any TJsonClass entries that have nothing in them.
MORE DETAIL
It seems the constructor works as expected, so no problem there. It also seems the correct way to destroy a Nullable<TObject> in the destructor is
if FProp.HasValue then
TObject(FProp).Free;
and to assign a nil to the property
TObject(x.Prop).Free;
x.Prop := nil;
and to assign a new non-nil object
TObject(x.Prop).Free;
x.Prop := TObject.Create;
If I make sure I do all this, then no leaks occur.
The file Neon.Tests.Types.Enums added to the Tests/Neon.Tests.Framework.dpr and .dproj in commit 10aa497 do not appear to actually have been committed to this repository.
Hello,
When a record embeds an object, the Serialization is done but Deserialization omits the object (would be recreated).
When a Sub-Type of TDateTime is used (TCustomDatetime = type TDateTime), it is serialized to Float.
type
TSubDateTime = type TDateTime;
TPersonObject=class
private
FP1 : Integer;
FP2 : TBytes;
FP3 : string;
public
property P1 : Integer read FP1 write FP1;
property P2 : TBytes read FP2 write FP2;
property P3 : string read FP3 write FP3;
end;
TPersonData=
record
First ,
Last : string;
Birth : TSubDateTime;
O : TPersonObject;
end;
procedure TForm1.SubTypeBtnClick(Sender: TObject);
var
LObject : TPersonObject;
Data : TPersonData;
LValue : TValue;
LJSON : TJSONValue;
sJSON : string;
begin
Data := default(TPersonData);
Data.O := TPersonObject.Create;
with Data.O do
begin
P1 := 11;
P2 := [65,66,67];
P3 := 'Object';
end;
with Data do
begin
First := 'First';
Last := 'Last';
Birth := Now;
end;
LValue := TValue.From(Data);
LJSON := TNeon.ValueToJSON(LValue);
sJSON := LJSON.ToJSON;
Memo1.Lines.Text := '----- Serialize -----';
Memo1.Lines.Add(sJSON);
FreeAndNil(LJSON);
FreeAndNil(Data.O);
/////////////////////////////////////////////////////////
LJSON := TJSONObject.ParseJSONValue(sJSON);
Data := TNeon.JSONToValue<TPersonData>(LJSON);
FreeAndNil(LJSON);
/////////////////////////////////////////////////////////
LValue := TValue.From(Data);
LJSON := TNeon.ValueToJSON(LValue);
sJSON := LJSON.ToJSON;
FreeAndNil(LJSON);
Memo1.Lines.Add('----- Deserialize/Serialize -----');
Memo1.Lines.Add(sJSON);
if Assigned(Data.O) then FreeAndNil(Data.O);
end;
Best regards.
Hi i'm having problem deserializing a complex class, i already seen the complex deserialization on the demos, but my class is little bit more complex, i have ObjectLists inside ObjectLists.
I don't know if i'm doing something wrong when declaring those models, but the property Numeros
which is a TObjectList<TNumero>
inside the property Enderecos
it's not being deserialized.
Here's my model classes(Getters and Setters implementation omitted for code clarity) :
TCliente :
type
TCliente = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
published
property idCliente: integer read FidCliente write SetidCliente;
property NomeFantasia: string read FNomeFantasia write SetNomeFantasia;
property Enderecos: TObjectList<TEndereco> read FEnderecos write SetEnderecos;
property NovoCampoString: string read FNovoCampoString write SetNovoCampoString;
end;
implementation
{ TCliente }
constructor TCliente.Create(AOwner: TComponent);
begin
inherited;
Enderecos := TObjectList<TEndereco>.Create;
end;
TEndereco:
type
TEndereco = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
published
property idCliente: integer read FidCliente write SetidCliente;
property idEndereco: integer read FidEndereco write SetidEndereco;
property Logradouro: string read FLogradouro write SetLogradouro;
property Bairro: string read FBairro write SetBairro;
property Cidade: string read FCidade write SetCidade;
property Numeros: TObjectList<TNumero> read FNumeros write SetNumeros;
end;
implementation
{ TEndereco }
constructor TEndereco.Create(AOwner: TComponent);
begin
inherited;
Numeros := TObjectList<TNumero>.Create(True);
end;
And TNumero:
type
TNumero = class(TComponent)
published
property Numero: string read FNumero write SetNumero;
property Cor: string read FCor write SetCor;
end;
implementation
When i manually populate the instance of TCliente class, serialization goes perfectly.
The Neon library serialize the following code :
Cliente := TCliente.Create(self);
Cliente.NomeFantasia := 'Alan';
Cliente.idCliente := 10;
Cliente.Senha := '1234';
Endereco := TEndereco.Create(self);
Endereco.idCliente := 10;
Endereco.idEndereco := 1;
Endereco.Logradouro := 'Rua salvador Simões';
Endereco.Bairro := 'Ipiranga';
Endereco.Cidade := 'São Paulo';
Numero := TNumero.Create(self);
Numero.Numero := '801';
Numero.Cor := 'Preto';
Endereco.numeros.Add(Numero);
Numero := TNumero.Create(self);
Numero.Numero := '111';
Numero.Cor := 'Amarelo';
Endereco.numeros.Add(Numero);
Cliente.Enderecos.Add(Endereco);
Endereco := TEndereco.Create(self);
Endereco.idCliente := 10;
Endereco.idEndereco := 2;
Endereco.Logradouro := 'Avenida Senador Vergueiro';
Endereco.Bairro := 'Jardim Hollywood';
Endereco.Cidade := 'São Bernardo do Campo';
Cliente.Enderecos.Add(Endereco);
LJSON := LWriter.ObjectToJSON(Cliente);
into this json :
{
"idCliente": 10,
"NomeFantasia": "Alan",
"Enderecos": [
{
"idCliente": 10,
"idEndereco": 1,
"Logradouro": "Rua salvador Simões",
"Bairro": "Ipiranga",
"Cidade": "São Paulo",
"Numeros": [
{
"Numero": "801",
"Cor": "Preto"
},
{
"Numero": "111",
"Cor": "Amarelo"
}
]
},
{
"idCliente": 10,
"idEndereco": 2,
"Logradouro": "Avenida Senador Vergueiro",
"Bairro": "Jardim Hollywood",
"Cidade": "São Bernardo do Campo",
"Numeros": []
}
],
"NovoCampoString": ""
}
But when deserialize this exactly same json into a TCliente object using this code :
var
LJSON: TJSONValue;
LReader: TNeonDeserializerJSON;
LConfig: INeonConfiguration;
Cliente: TCliente;
LWriter: TNeonSerializerJSON;
begin
memoError.Lines.Clear;
mmoDeSerializar.Lines.Clear;
LJSON := TJSONObject.ParseJSONValue(Memo1.Lines.Text); // < same json generated by the serialization
Cliente := TCliente.Create(self);
try
LConfig := TNeonConfiguration.Default.SetVisibility([mvPublished]).SetPrettyPrint(True);
LReader := TNeonDeserializerJSON.Create(LConfig);
try
LReader.JSONToObject(Cliente, LJSON);
memoError.Lines.AddStrings(LReader.Errors);
LWriter := TNeonSerializerJSON.Create(LConfig);
mmoDeSerializar.Lines.Add(LWriter.ObjectToJSON(Cliente).ToString);
finally
LReader.Free;
end;
finally
Cliente.Free;
LJSON.Free;
end;
end;
The Neon library don't deserialize the "Numeros" object, just the "Enderecos"
Here is the result:
{
"idCliente": 10,
"NomeFantasia": "",
"Enderecos": [
{
"idCliente": 10,
"idEndereco": 1,
"Logradouro": "Rua salvador Simões",
"Bairro": "Ipiranga",
"Cidade": "São Paulo"
},
{
"idCliente": 10,
"idEndereco": 2,
"Logradouro": "Avenida Senador Vergueiro",
"Bairro": "Jardim Hollywood",
"Cidade": "São Bernardo do Campo"
}
],
"NovoCampoString": ""
}
Please, can you help-me find what's wrong ?
Thank you!
The functions TNeonDeserializerJSON.ReadFloat and TNeonDeserializerJSON.ReadInt64 in the Source/Neon.Core.Persistence.JSON.pas cause compilation errors in my Delphi version 10.2.
As it turns out System.JSON.TJSONNumber.AsType<T>
(line 1523) is only supported from Delphi versions 10.3 up.
See https://docwiki.embarcadero.com/Libraries/Tokyo/en/System.JSON.TJSONNumber_Methods and https://docwiki.embarcadero.com/Libraries/Rio/en/System.JSON.TJSONNumber_Methods
And System.Rtti.TValue.Make<T>
(line 1553) is only supported from Delphi 10.4 up.
See https://docwiki.embarcadero.com/Libraries/Rio/en/System.Rtti.TValue.Make and https://docwiki.embarcadero.com/Libraries/Sydney/en/System.Rtti.TValue.Make
Best regards,
Marcus
Unfortunately i don't remember details (fixed that in my project more than 3 months ago) but:
There are problems with deserialization of unsigned types (cardinal/dword/etc).
when you trying to deserialize json to object which has cardinal field and you have numberic value in src json which is greater than maxint - you will receive convert error.
borrowed solution:
unit ReqRes.JSON.Cardinal;
interface
uses
Neon.Core.Persistence;
procedure RegisterCardinalSerializers(ARegistry: TNeonSerializerRegistry);
implementation
uses
System.TypInfo,
System.Json,
System.Rtti,
System.SysUtils,
ReqRes.JSON,
Neon.Core.Utils,
Neon.Core.Types,
Neon.Core.Serializers.RTL;
type
TCardinalDigestSerializer = class(TCustomSerializer)
protected
class function GetTargetInfo: PTypeInfo; override;
class function CanHandle(AType: PTypeInfo): Boolean; override;
public
function Serialize(const AValue: TValue; ANeonObject: TNeonRttiObject; AContext: ISerializerContext): TJSONValue; override;
function Deserialize(AValue: TJSONValue; const AData: TValue; ANeonObject: TNeonRttiObject; AContext: IDeserializerContext): TValue; override;
end;
{ TCardinalDigestSerializer }
class function TCardinalDigestSerializer.CanHandle(AType: PTypeInfo): Boolean;
begin
Result := AType = GetTargetInfo;
end;
function TCardinalDigestSerializer.Deserialize(AValue: TJSONValue;
const AData: TValue; ANeonObject: TNeonRttiObject;
AContext: IDeserializerContext): TValue;
var
lValue: Cardinal;
begin
if AValue is TJSONNull then
Exit(TValue.From<Cardinal>(0));
lValue := StrToUInt(AValue.Value);
Result := TValue.From<Cardinal>(lValue);
end;
class function TCardinalDigestSerializer.GetTargetInfo: PTypeInfo;
begin
Result := System.TypeInfo(Cardinal);
end;
function TCardinalDigestSerializer.Serialize(const AValue: TValue; ANeonObject: TNeonRttiObject; AContext: ISerializerContext): TJSONValue;
begin
Result := TJSONNumber.Create(AValue.AsType<Cardinal>);
end;
procedure RegisterCardinalSerializers(ARegistry: TNeonSerializerRegistry);
begin
ARegistry.RegisterSerializer(TCardinalDigestSerializer);
end;
end.
e.g.
class function Print(AJSONValue: TJSONValue; OutputOptions: TJSONAncestor.TJSONOutputOptions; APretty: Boolean): string; overload; static;
var
LWriter: TStringWriter;
begin
LWriter := TStringWriter.Create;
try
TNeon.PrintToWriter(AJSONValue, LWriter, OutputOptions, APretty);
Result := LWriter.ToString;
finally
LWriter.Free;
end;
end;
class procedure TNeon.PrintToWriter(AJSONValue: TJSONValue; AWriter: TTextWriter;
OutputOptions: TJSONAncestor.TJSONOutputOptions; APretty: Boolean);
var
LJSONString: String;
begin
LJSONString := AJSONValue.ToJSON(OutputOptions);
PrintToWriter(LJSONString, AWriter, APretty);
end;
The library currently compiles with the following hints/warnings:
[dcc32 Warning] Neon.Core.Persistence.JSON.pas(1504): W1002 Symbol 'MinExtended' is specific to a platform
[dcc32 Warning] Neon.Core.Persistence.JSON.pas(1505): W1002 Symbol 'MaxExtended' is specific to a platform
[dcc32 Hint] Neon.Core.Persistence.JSON.pas(1492): H2077 Value assigned to 'LMin' never used
[dcc32 Hint] Neon.Core.Persistence.JSON.pas(1498): H2077 Value assigned to 'LMin' never used
[dcc32 Hint] Neon.Core.Persistence.JSON.pas(1504): H2077 Value assigned to 'LMin' never used
[dcc32 Hint] Neon.Core.Persistence.JSON.pas(1510): H2077 Value assigned to 'LMin' never used
[dcc32 Hint] Neon.Core.Persistence.JSON.pas(1516): H2077 Value assigned to 'LMin' never used
[dcc32 Warning] Neon.Core.Persistence.JSON.pas(1534): W1036 Variable 'LMax' might not have been initialized
You can test it with the NeonMainDemo project:
Thanks and great job!
I had to add 'public' to let 'overload' method accepted. With visibility default 'published' the class did not compile.
TBase64 = class
public
class function Encode(const ASource: TBytes): string; overload;
class function Encode(const ASource: TStream): string; overload;
You have an example line in the Readme file that is:
Memo1.Lines.Text := TJSONUtils.PrettyPrint(LJSON);
But it there does not appear to be a PrettyPrint method in the TJSONUtils class. And it is managed by the Serializer Config.
Please I am trying a single sample using your demo:
TResponseConsoleMessage = class
private
Fname : String;
Fvalue : String;
public
property name : String read Fname;
property value : String read Fvalue;
end;
Then I use:
procedure TfrmSerializationComplex.Button1Click(Sender: TObject);
var
LChats: TResponseConsoleMessage;
LJSON: TJSONValue;
begin
MemoW.Lines.Text:='{"name":"DomCompleted","value":"teste"}';
LChats := TResponseConsoleMessage.Create();
LJSON := TJSONObject.ParseJSONValue(MemoW.Lines.Text);
try
TNeon.JSONToObject(LChats, LJSON, frmConfiguration.BuildSerializerConfig);
finally
LJSON.Free;
LChats.Free;
end;
end;
But the problem is the Object LChats is always empty. The fields contain empty string after to use JsonToObject.
What am I doing wrong?
I'm experiencing an access violation issue when there is a high level of concurrency with multithreading. I have a service that receives JSON data to insert a record into a database table. I'm using Neon to deserialize the JSON and map it to a class.
When I receive a request in my service, I handle it like this:
TTask.Run(
procedure
begin
TPersistencia.Insert(StompFrame.Body, FConf, StompFrame, Self.FStompClient);
end
);
In my tests, I sent the exact same JSON 1000 times, and occasionally, during the execution of the process, an access violation occurs when TNeonDeserializerJSON attempts to read the JSON to populate the class:
LReader := TNeonDeserializerJSON.Create(LConfig);
LReader.JSONToObject(LClasseTabela, LJSON); //<< AV occurs here
If I remove TTask, it works fine but is slower, and I need it to be faster. How can I address this problem?
Hello Paolo, congratulations on your fantastic library, i believe its the best i've tested so far.
I'm only missing one thing, there's a way to print null values on Deserializing ?
I have a nullable property and i want to show it on json body like "MyProperty": null, its possible ?
i'm trying with the demo, but can't get the null values to show.
Thank you in advance!
Dictionaries (key must be of type string)
Is it possible that this restriction will be removed?
In Neon.Core.Persistence.JSON.
function : TNeonDeserializerJSON.ReadArray
Lines : 1228
LItemValue := ReadDataMember(LParam, Result, True);
Should be follow:
LItemValue := ReadDataMember(LParam, AData.GetArrayElement(LIndex), True);
For example, There is no TJsonBool object in XE7.
To test it you only have to change
TEnumSpeed = (Low, Medium, High);
to
TEnumSpeed = (Low=1, Medium=2, High=3);
in Demo.Neon.Entities unit. Then press TMyRecord button.
Demos/Main/Demo.Forms.Details.pas references an absolute path on D:\
Also, the working directory is set to the .exe output directory (the Win32\Debug
subfolder).
Add a new field to your TPerson class in Demo.Neon.Entities for this TExampleCMR to replicate "invalid pointer operation" exception on serialization
TExampleCMR = Record
Public
Info:string;
KeyPairs: TDictionary<string, string>;
class operator Initialize(out Dest:TExampleCMR);
class operator Finalize(var Dest:TExampleCMR);
End;
...
class operator TExampleCMR.Initialize(out Dest:TExampleCMR);
begin
Dest.KeyPairs := TDictionary<string, string>.Create;
end;
class operator TExampleCMR.Finalize(var Dest:TExampleCMR);
begin
Dest.KeyPairs.Free;
end;
As per title, in Neon.Core.Utils
, the compiler reports a warning of:
[dcc32 Warning] Neon.Core.Utils.pas(468): W1035 Return value of function 'TRttiUtils.ForEachFieldWithAttribute<T>' might be undefined
When I run the test project I get the following errors:
Failing Tests
Neon.Tests.Types.Simple.TTestSimpleTypes.TestDateTime.TestDateOnly
Message: Expected "2019-12-23T00:00:00.000Z" is not equal to actual ""
Neon.Tests.Types.Simple.TTestSimpleTypes.TestDateTime.TestDateTime
Message: Expected "2019-12-23T01:01:01.000Z" is not equal to actual ""```
Hello,
Please note that this exception is raised when this record needed to be serialized:
TFaceLandmark =
record
KeyPoints : array[1..68] of TPointFloat;
end;
Project raised exception class EInsufficientRtti with message 'Insufficient RTTI available to support this operation'.
Best regards.
I Have a record with arrays inside it as follows..
And get an Insufficient RTTI support?
Thanks
Phil
Passing a record to TNeon.ObjectToJSON doesn't work, and a class containing an array will not be completely serialised (the array field won't even be in the JSON).
Hello,
Is it possible to include into serialized Json only fields having values different from default (Empty) ones: 0, '', [], ...
Best regards.
Hi,
i'm not sure, but from my perpective there is a bug in serialization of properities:
[NeonInclude(IncludeIf.NotEmpty)]
property description: string read Getdescription;
"description" will not be serialized when Getdescription() returns a value ...
I'd expect the serialization of "description" in this case ..
Greetings Andy
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.