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?
Asked Answered
B

1

7

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.

Bathsheb answered 1/7, 2015 at 16:22 Comment(7)
It's not at all clear to me that the generics are doing you any good here. This feels like something where you want runtime binding rather than compile time.Pensioner
The generics are there mainly so a developer designing a new message doesn't need to inherit from a specific class or implement a specific interface. They just need to supply a new class and a handler that knows what to do with it. In any case this library was designed by a coworker using C# and I've been porting it to Delphi. I've attempted to keep the public API as close as possible to the C# implementation.Bathsheb
I don't really see why generics are needed for that. And you are constrained by having to instantiate all generic types at compile time.Pensioner
What would you recommend instead?Bathsheb
A container holding a metaclass and the handler. You don't appear to be doing anything that needs to know about the type. All you need is thee metaclass to uniquely identify it. I could be wrong though. As for how to identify the class in JSON, how about its name?Pensioner
I didn't include the code for it but the TRequestHandler<TRequest,TResponse> constructor is taking the types for the request and response as metaclasses. The main issue is deserializing the JSON using the metaclass. TJson.JsonToObject doesn't expose a method that accepts a metaclass, TJsonObject and returns an object.Bathsheb
If you are accepting meta classes as params in the constructor, why make the class generic? You're just repeating yourself. Personally I'd give JsonToObject a wide berth. Doesn't Spring4D have quality libs for this?Pensioner
F
2

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.

Fort answered 2/7, 2015 at 9:29 Comment(1)
Is TJson a stardard Delphi class? Which unit is it in XE?Thomson

© 2022 - 2024 — McMap. All rights reserved.