Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I store the type parameter(s) of a parameterized method and later use them to convert a JSON object to a plain object of the generic type?

I am attempting to write a generic messaging passing system for Delphi and .NET. The system allows messages to be defined as plain objects and message handlers are defined as anonymous methods that act on those objects.

The objects are converted to JSON and passed between applications running on the same machine. Each application maintains a list of handlers that understand specific message types.

My class has a number of parameterized registration methods. Some take a single type parameter. Some take a pair of parameters where one represents a request object and the other a response object. Here is what the Request/Response handler registration looks like:

procedure TTelegraph.RegisterRequestHandler<TRequest, TResponse>
  (requestTypeToHandle: string; handler: TFunc<TRequest, TResponse>);
begin
  FRequestHandlers.Add(requestTypeToHandle,
    TRequestHandler<TRequest, TResponse>.Create(handler,
    TRequest,
    TResponse));
end;

FRequestHandlers is a TDictionary<string,TRequestHandler>. The registration method is called like so:

  FTelegraph.RegisterRequestHandler<TTestRequest, TTestResponse>('My Request', 
    function(x: TTestRequest): TTestResponse
    begin
      Result := TTestResponse.Create;
      Result.Number := x.Number;
      Result.Message := Format('Received: %s', [x.Message]);
    end);

The generic TRequestHandler<T1,T2> is a DTO that wraps the handler along with the types TRequest and TResponse. It inherits from the non-generic TRequestHandler. I'm not sure if there's a better way to go about this but it was the only way I could think of to store multiple unrelated types in a single collection.

This all seems to work fine. The problem is when a request message is received. The C# code to handle request messages looks like this:

private void ProcessRequestTelegram(Telegram request)
{
    var requestType = _RequestHandlers[request.MessageType].RequestType;
    var typedRequest = JsonConvert.DeserializeObject(request.Payload, requestType);
    var result = _RequestHandlers[request.MessageType].Handler.DynamicInvoke(typedRequest);
    var jsonPayload = JsonConvert.SerializeObject(result);
    var response = new Telegram()
    {
        Payload = jsonPayload,
        CorrelationID = request.CorrelationID,
        Template = TelegramTemplateEnum.Response,
        SenderWindowHandle = _LocalWindowHandle.ToInt32(),
        RecipientWindowHandle = _MessageRecipient.ToInt32(),
        MessageType = request.MessageType
    };
    var jsonResponse = JsonConvert.SerializeObject(response);

    var resultCode = _Messaging.SendMessage(_MessageRecipient, jsonResponse);
    CheckIfSendMessageErrorOccurred(resultCode);
}

In the code above RequestType is of the Type type.

But for the life of me I can't come up with the Delphi equivalent. I get as far as attempting to deserialize the request.Payload and I'm stuck at how to pass the JSON parser the type to convert the payload into. I've tried various ways of storing TRequest and TResponse in the RequestType and ResponseType properties of TRequestHandler: TTypeInfo, TRTTIType and currently TClass. But nothing seems to give me anything useful to pass to TJson.JsonToObject. It has a generic overload that takes a type parameter for the return type but apparently you can't use a TClass as a type parameter.

like image 440
Kenneth Cochran Avatar asked Jul 01 '15 16:07

Kenneth Cochran


1 Answers

You cannot use TJson.JsonToObject<T> as at the time of calling this you don't have a generic T. Look into the code of this method and you see that T is being passed to TJSONUnMarshal.CreateObject. So in your case you should store the TClass of TRequest and TResponse and write your own version of TJson.JsonToObject that gets passed a TClass.

Another way would be to create your instance first and then pass it to the non generic TJson.JsonToObject which takes the ClassType of the passed instance and passes it to TJSONUnMarshal.CreateObject.

like image 88
Stefan Glienke Avatar answered Nov 15 '22 01:11

Stefan Glienke