3

I'd like to serialize objects and records to send them and restore on a remote endpoint. There is the shared objects declaration unit for a local and remote end.

I has a record with a field containing different records as JSON Object:

TPmMessage = record
  CorrelationId: TGUID;
  BatchId: TGUID;
  MessageName: string; 
  Data: ISuperObject; // TAlarms, TCommand record and etc. can be inside as JSON object
end;

CASE 1

I need to write a Type name (TRttiType.QualifiedName) in a JSON object for the Data field for deserialization on other side.

An example structure:

  TDeviceInfo = record
    DeviceType: string;
    DeviceIp: string;
  end;

  TAlarmLocation = record
    Name: string;
    rack: Word;
    slot: Word;
    port: Word;
  end;

  TAlarmInfo = record
    AlarmType: string;
    Severity: string;
    ConditionType: string;
    Datetime: TDateTime;
    Location: TAlarmLocation;
  end;

  TAlarm = record
    DeviceInfo: TDeviceInfo;
    Alarm: TAlarmInfo;
  end;

  TAlarmsList = array of TAlarmInfo;

Usage:

var
  msg: TPmMessage;
  als: TAlarms;
  ctx: TSuperRttiContext;
  JsonText: string;
begin
  ctx := TSuperRttiContext.Create;
  try
  SetLength(als,2);
  // init array of als
  ...
  // init TPmMessage Fields
  ...
  // serialize TAlarms
  msg.Data := ctx.AsJson<TAlarms>(als);
  // serialize TPmMessage
  JsonText := ctx.AsJson<TPmMessage>(msg).AsString;
  ...
  // Restore record from JSON object
  msg := ctx.AsType<TPmMessage>(SO(JsonText));
  ...
  finally
    ctx.Free;
  end;
end;

After restore I get an TPmMessage, but I don't know what type in the Data JSON object.

CASE 2

In case 1 I don't know the type of the Data JSON object. So I added the DataObjectType field with QualifiedName as value.

In that case I make another structure for seralization:

  TPmMessageData = record
    DataObjectType: string;
    DataObject: ISuperObject;
  end;

  TPmMessage = record
    Source: string;
    CorrelationId: TGUID;
    BatchId: TGUID;
    MessageName: string;
    Data: TPmMessageData;
  end;

This structure serialized correctly with code:

var 
  msg: TPmMessage;
  ti: PTypeInfo;
  uals: TAlarms;
begin
  {fill Alarms array}
  ti := TypeInfo(TAlarms);
  msg.Data.DataObjectType := ctx.Context.GetType(ti).QualifiedName;
  msg.Data.DataObject := ctx.AsJson<TAlarms>(uals);
end;

DataObject: ISuperObject also serialised well.

A question is How to deserialize the DataObject?

I thought that it's possioble to use

{var DataType: TRttiType;}
DataType := ctx.Context.FindType(DecodedMsg.Data.DataObjectType);
uals := ctx.AsJson<DataType>(DecodedMsg.Data.DataObject);

But the Method 'AsType' requires explicit type argument.

So I think to use case to set the correct type is the only way.

How to serialize/deserialise by using superobject beter?

Could you suggest me a better structure for the messages instead of this?

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
r3code
  • 51
  • 1
  • 6
  • Not a direct answer, since it is not about *SuperObject*, but check out how JSON serialiazation is implemented in our [mORMot](http://mormot.net) framework: you are able to have *custom serialization* of [any record, class or dynamic array](http://blog.synopse.info/post/2012/05/03/Custom-JSON-serialization-of-records), even with older versions of Delphi (tested with Delphi 6 and up), just by defining some methods. You may follow the same pattern to add it to *SuperObject*. – Arnaud Bouchez Sep 03 '12 at 13:22
  • @ArnaudBouchez Thanks! That may be good direction for the thought! – r3code Sep 04 '12 at 05:59

1 Answers1

1

You could wrap the JSON serialized object in a container object which has two properties:

  • type name
  • the wrapped object

then in the deserialization, first read the type of the contained object and then load the serialized object into a Delphi object variable of the corresponding type.

However, a interface pointer in a record is not easy to serialize anyway (it contains a pointer instead of a full JSON encoded string) - so I am not sure if the record serialization fails for a different reason.

mjn
  • 36,362
  • 28
  • 176
  • 378
  • HaHa. I did this but not post here. My implementataion for this case was `TPmMessageData = record DataObjectType: string; DataObject: ISuperObject; end;` and `TPmMessage = record Source: string; CorrelationId: TGUID; BatchId: TGUID; MessageName: string; Data: TPmMessageData; end;` – r3code Sep 04 '12 at 05:55
  • I was thinking I can hide all the work in the Converter: `ctx.SerialToJson.add(TypeInfo(TPmMessage), PmMessageToJSon);` and the Inverter `ctx.SerialFromJson.add(TypeInfo(TPmMessage), PmMessageFromJSon);` as it was shown in the article ["TSuperRTTIContext and nested TObjects + TObjectLists"](http://www.progdigy.com/forums/viewtopic.php?t=4612). But I don't have much experience with [superobject](http://code.google.com/p/superobject/) marshallers code so my realization fall into a recusion. – r3code Sep 04 '12 at 07:47
  • Do you know how to "find the type of the contained object and then load the serialized object into a Delphi object variable of the corresponding type."? – r3code Sep 18 '12 at 07:10
  • @dr.eel updated my answer - the serializer writes the class name (or a abstract type identifiert) in the type name field of the JSON document, so the deserializer can read it and Delphi code then can switch to pass the correct object for deserialization – mjn Sep 18 '12 at 07:19
  • I have updated my post - so please take a look at section **Case 2**. You think that using `case` is only the one way to pass the correct type to `AsType`? – r3code Sep 18 '12 at 09:57