Storing json blobs in db with class inheritance

I want to be able to serialise/deserialise json with class hierarchies. XData deals with this neatly by injecting the '$xdata.type' property into the json output.

Is there a simple way to reuse the xdata json converters to serialise/deserialise json strings to objects which can be stored in dbs. There are multiple ways to do simple encoding, but as XData already deals with this inheritance, I am ideally looking for a minimal implementation within an xdata server to accomplish this. I'm digging through the code, but just hoping someone has done this before or has some guidance to speed up the investigation.

just to illustrate what I'm trying to do:
type
TBase = class
// ....
end;
TDerived = class(TBase)
// ...
end;

...

procedure MyXdataService.DoSomething;
var
json : string;
obj : TBase;
begin
obj := TDerived.create;
json := toJson(obj);
// store json in db...
end;



Sorted.



procedure MyXdataService.DoSomething;
var
    json : string;
    obj : TBase;
    serializer: TXDataJsonClientSerializer;
begin
    obj := TDerived.create;
    serializer := TXDataJsonClientSerializer.Create(TXDataOperationContext.Current.Handler.XModel);
    json := serializer.WriteAs(obj);
    serializer.free;
    // do something with the json
end;

Actually, not sure how to use the deserialiser. I'm not finding the IJsonReferenceSolver.


TXDataRequestHandler has a CreateSolver(), but no public property I can see.

You can pass nil as the solver, it's just used to load the lazy-load the proxied associations.

Thank you Wagner.


Unfortunately, i can't get it to work. 

    d :=  TXDataJsonClientDeserializer.Create(TXDataOperationContext.Current.Handler.XModel, nil);
    obj := d.Read<TBase>(json);
    d.Free;
    // obj = nil


I'm I going down the right route in trying to deserialise this way. the 'json' has the correct reference to the type.

Could it be because I'm using normal podo classes rather than entity annotated classes?

Why? What is the problem with it?

Note that the deserializer "owns" the objects it creates, thus destroying the deserializer (d.free) will also destroy the object TBase referenced by variable obj.

I can confirm it works. I must have done something strange originally.

I created a minimal example which I'll attach for anyone else as reference:

----------------------------- Project1.pas----------------------------- 
program Project1;

{$APPTYPE CONSOLE}
{$R .res}

uses
  System.SysUtils,
  Sparkle.Indy.Server,
  XData.Service.Common,
  XData.Server.Module,
  XData.OpenAPI.Service,
  Unit1 in 'Unit1.pas';



var

  SparkleServer: TIndySparkleHTTPServer;
  Module: TXDataServerModule;

begin
  SparkleServer := TIndySparkleHTTPServer.Create(nil);
  SparkleServer.DefaultPort := 8080;
  Module := TXDataServerModule.Create('/test');
  Module.AccessControlAllowOrigin := '';
  SparkleServer.Dispatcher.AddModule(Module);
  SparkleServer.Active := true;
  RegisterOpenAPIService();

  writeln('listening on port ', SparkleServer.DefaultPort);
  writeln('');
  writeln('');
  writeln('press enter to terminate');
  readln;
  writeln('done');
  SparkleServer.Active := false;
end.
-----------------------------Unit1.pas-----------------------------
unit Unit1;

interface

uses
  System.SysUtils,
  XData.Service.Common,
  XData.Server.Module,
  XData.OpenAPI.Service,
  XData.Json.Serializer,
  XData.Json.Deserializer;

type
  TLifeForm = class
  private
    FType: string;
  public
    property LifeForm: string read FType write FType;
  end;

  TMammal = class(TLifeForm)

  end;

  TPerson = class(TMammal)
  private
    FName: string;
  public
    property Name: string read FName write FName;
  end;

  [ServiceContract]
  IMyService = interface(IInvokable)
    ['{8F4A594D-B611-4FE9-8B95-F60D77FCFE29}']
    [HttpGet]
    function Test(): string;
  end;

  [ServiceImplementation]
  TMyService = class(TInterfacedObject, IMyService)
  public
    function Test(): string;
  end;

implementation

function TMyService.Test(): string;
var
  base: TLifeForm;
  person: TPerson;
  Serializer: TXDataJsonClientSerializer;
  Deserializer: TXDataJsonClientDeserializer;
begin

  person := TPerson.Create();
  person.LifeForm := 'person';
  person.name := 'Joe';

  Serializer := TXDataJsonClientSerializer.Create(TXDataOperationContext.Current.Handler.XModel);
  try
    result := Serializer.WriteAs(person);
    writeln('serialised', result);

    Deserializer := TXDataJsonClientDeserializer.Create(TXDataOperationContext.Current.Handler.XModel, nil);
    try
      base := Deserializer.Read<TLifeForm>(result); // NOTE: reference to base class 

      result := Serializer.WriteAs(base);
      writeln('expecting serialised above: ', result);

    finally
      Deserializer.free;

    end;
  finally
    Serializer.free;
  end;

end;

initialization

  RegisterServiceType(TMyService);

end.
-----------------------------console output-----------------------------
listening on port 8080


press enter to terminate
serialised{"$id":1,"@xdata.type":"Unit1.TPerson","Type":"person","Name":"Joe"}
expecting serialised above: {"$id":2,"@xdata.type":"Unit1.TPerson","Type":"person","Name":"Joe"}
-----------------------------

I experimented some more... I moved the deserialisation/serialisation to a helper class and actually - it all works fine. I must have done something silly before.



thanks.
conrad

--------------------------------unit1.pas----------
unit Unit1;

interface

uses
  System.SysUtils,
  XData.Service.Common,
  XData.Server.Module,
  XData.OpenAPI.Service,
  XData.Json.Serializer,
  XData.Json.Deserializer;

type

  TExpr = class

  end;

  TIntValueExpr = class(TExpr)
  private
    FValue: integer;
  public
    constructor Create(value: integer);
    property value: integer read FValue write FValue;
  end;

  TDoubleValueExpr = class(TExpr)
  private
    FValue: double;
  public
    constructor Create(value: double);
    property value: double read FValue write FValue;
  end;

  TUnaryOpExpr = class(TExpr)
  private
    FOperation: char;
    FExpr: TExpr;
  public
    constructor Create(op: char; expr: TExpr);
    property Operation: char read FOperation write FOperation;
    property expr: TExpr read FExpr write FExpr;
  end;

  TBinOpExpr = class(TExpr)
  private
    FOperation: char;
    FLeft: TExpr;
    FRight: TExpr;
  public
    constructor Create(Left: TExpr; op: char; right: TExpr);
    property Operation: char read FOperation write FOperation;
    property Left: TExpr read FLeft write FLeft;
    property right: TExpr read FRight write FRight;
  end;

  [ServiceContract]
  IMyService = interface(IInvokable)
    ['{8F4A594D-B611-4FE9-8B95-F60D77FCFE29}']
    [HttpGet]
    function Test(): string;
  end;

  [ServiceImplementation]
  TMyService = class(TInterfacedObject, IMyService)
  public
    function Test(): string;
  end;

implementation

type
  TJsonHelper = class
    class function ToJson<T>(const value: T): string; static;
    class function FromJson<T>(const Json: string): T; static;
  end;

function TMyService.Test(): string;
var
  expr: TExpr;

begin

  expr := TBinOpExpr.Create(TIntValueExpr.Create(1), '+', TUnaryOpExpr.Create('-', TDoubleValueExpr.Create(345.67)));

  result := TJsonHelper.ToJson(expr);
  writeln('serialised', result);

  expr := TJsonHelper.FromJson<TExpr>(result);
  result := TJsonHelper.ToJson(expr);
  writeln('expecting serialised above: ', result);
end;


constructor TIntValueExpr.Create(value: integer);
begin
  FValue := value;
end;

{ TBinOpExpr }

constructor TBinOpExpr.Create(Left: TExpr; op: char; right: TExpr);
begin
  FLeft := Left;
  FRight := right;
  FOperation := op;
end;

{ TUnaryOpExpr }

constructor TUnaryOpExpr.Create(op: char; expr: TExpr);
begin
  FOperation := op;
  FExpr := expr;
end;

{ TDoubleValueExpr }

constructor TDoubleValueExpr.Create(value: double);
begin
  FValue := value;
end;

{ TJsonHelper }

class function TJsonHelper.FromJson<T>(const Json: string): T;
var

  Deserializer: TXDataJsonClientDeserializer;
begin
  Deserializer := TXDataJsonClientDeserializer.Create(TXDataOperationContext.Current.Handler.XModel, nil);
  try
    result := Deserializer.Read<T>(json);
  finally
    Deserializer.free;
  end;
end;

class function TJsonHelper.ToJson<T>(const value: T): string;
var
  Serializer: TXDataJsonClientSerializer;
begin
  Serializer := TXDataJsonClientSerializer.Create(TXDataOperationContext.Current.Handler.XModel);
  try
    result := Serializer.WriteAs(value);
  finally
    Serializer.free;
  end;
end;

initialization

RegisterServiceType(TMyService);

end.
-----------------------------------------console output------------------------------------------------------------
listening on port 8080


press enter to terminate
serialised{"$id":1,"@xdata.type":"Unit1.TBinOpExpr","Operation":"+","Left":{"$id":2,"@xdata.type":"Unit1.TIntValueExpr","Value":1},"Right":{"$id":3,"@xdata.type":"Unit1.TUnaryOpExpr","Operation":"-","Expr":{"$id":4,"@xdata.type":"Unit1.TDoubleValueExpr","Value":345.67}}}
expecting serialised above: {"$id":1,"@xdata.type":"Unit1.TBinOpExpr","Operation":"+","Left":{"$id":2,"@xdata.type":"Unit1.TIntValueExpr","Value":1},"Right":{"$id":3,"@xdata.type":"Unit1.TUnaryOpExpr","Operation":"-","Expr":{"$id":4,"@xdata.type":"Unit1.TDoubleValueExpr","Value":345.67}}}

----------------------------------------------------------------------------------