Search code examples
c#websocketjson-rpc

Deserializing received Json-Rpc data via Websocket API


I'm trying to figure out a way to create a generic Websocket JsonRpc client.

After connecting I'm starting a loop to listen for data coming from the WebSocket API and sending that data to the event as a string. I'd like to return a generic JsonRpcResponse<T> object instead but not sure how or if it's possible. Where JsonRpcResponse has an Id and Method fields as defined in the spec and T is a specific type depending on the data received.

From what I've seen there's no way to use generics here since I'd have to call another event with a non-generic type or object to pass that data along.

public event Func<string, Task>? OnDataReceived;

public async Task ConnectAsync()
{
    Uri uri = new Uri(_url);
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000);
    await _client.ConnectAsync(uri, cancellationTokenSource.Token);

    // Start listening for data
    await Task.Factory.StartNew(async () =>
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        while (_client.State == WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested)
        {
            var receivedData = await ReadStringAsync(cancellationTokenSource.Token);
            if (OnDataReceived != null)
            {
                await OnDataReceived.Invoke(receivedData);
            }

        }
    };
}

Solution

  • How about this:

    namespace GenericJsonResponse {
    
        /// <summary>
        /// Desired returned type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class JsonResponse<T>  {
            public T? Data { get; set; }
            public int Id { get; set; } = 0;
            public string Method { get; set; } = string.Empty;
    
            /// <summary>
            /// Convert string to anew instance
            /// </summary>
            /// <param name="json"></param>
            /// <returns></returns>
            /// <exception cref="NotImplementedException"></exception>
            /// <remarks>will have to add to each sub class</remarks>
            public static T FromJson(string json) {
                throw new NotImplementedException();
            }
        }
    
        /// <summary>
        /// Generic socket listener / sub class per type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class GenericSocketListener<T> {
            /// <summary>
            /// exposes event with generic response 
            /// </summary>
            public event Func<string, Task<JsonResponse<T>>>? OnDataReceived;
    
            /// <summary>
            /// Listen to socket
            /// </summary>
            /// <param name="token"></param>
            /// <returns></returns>
            public virtual async Task Listen( CancellationToken token) {
                string data = string.Empty;
                while (! token.IsCancellationRequested) {
                    // listen... and get typed result, save string to data
                    JsonResponse<T> result = await OnDataReceived.Invoke(data);
                }
            }
        }
    
        /// <summary>
        /// Dummy poco
        /// </summary>
        public class Person {
            public string FirstName { get; set; } = string.Empty;
            public string LastName { get; set; } = string.Empty;
        }
    
        /// <summary>
        /// Class that will listen to people
        /// </summary>
        public class PersonSocketListener : GenericSocketListener<Person> { 
        }
    }