Dynamically map JSON commands to object methods in .Net

Lately I've been playing around with USB led lights in .Net. I wanted the animations to be separated from my code. Wouldn't it be great if you could define what animations are executed in a JSON file? And map it to code?

Normally one would build a mapper that does the conversion of the JSON commands to the methods. I like to use a more generic approach. I've created a small utility class that executes commands by mapping and executing them as a method of the object. The solution is workable for a host of scenarios:

  • You can use it to map a JSON RPC command from a webservice on an object, creating a more generic service.
  • You can now create a JSON file with USB led animations specification that need to be executed when a certain event occurs.
  • Simplifying the mapping between commands and objects in a generic way.

Nuget

Before you start, there is a Nuget package. (Link to GitHub at the end of the blog.)

  • Install-Package KeesTalksTech.Utilities.Rpc -Version 0.3.2
  • dotnet add package KeesTalksTech.Utilities.Rpc --version 0.3.2
  • <PackageReference Include="KeesTalksTech.Utilities.Rpc" Version="0.3.2" />

Remote Timer Example

I'll show you how things work using an example. Let's create a remotely controlled timer. Commands on the timer are actions like start and stop.

/// <summary>
/// Indicates the object implements a timer.
/// </summary>
public interface IRemoteTimer
{
    /// <summary>
    /// Gets a value indicating whether this instance is running.
    /// </summary>
    /// <value>
    /// <c>true</c> if this instance is running; otherwise, <c>false</c>.
    /// </value>
    bool IsRunning
    {
        get;
    }

    /// <summary>
    /// Gets the ticks total.
    /// </summary>
    /// <returns>The ticks.</returns>
    int GetTicksTotal();

    /// <summary>
    /// Sets the interval.
    /// </summary>
    /// <param name = "newInterval">The new interval.</param>
    void SetInterval(double newInterval);

    /// <summary>
    /// Starts this instance.
    /// </summary>
    void Start();

    /// <summary>
    /// Stops this instance.
    /// </summary>
    void Stop();
}

Let's implement the class:

using System;
using System.Timers;

public class RemoteTimer : IRemoteTimer, IDisposable
{
    private Timer timer;
    private int ticks = 0;

    /// <summary>
    /// Gets the interval.
    /// </summary>
    /// <value>
    /// The interval.
    /// </value>
    public double Interval
    {
        get;
        private set;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref = "RemoteTimer"/> class.
    /// </summary>
    /// <param name = "interval">The interval.</param>
    public RemoteTimer(double interval)
    {
        SetInterval(interval);
    }

    /// <summary>
    /// Sets the interval.
    /// </summary>
    /// <param name = "interval">The interval.</param>
    public void SetInterval(double interval)
    {
        var isRunning = (timer?.Enabled).GetValueOrDefault();
        timer?.Stop();
        timer?.Dispose();
        timer = new Timer(interval);
        timer.Elapsed += (s, e) => ticks++;
        Interval = interval;
        if (isRunning)
        {
            Start();
        }
    }

    /// <summary>
    /// Starts this instance.
    /// </summary>
    public void Start()
    {
        timer.Start();
    }

    /// <summary>
    /// Stops this instance.
    /// </summary>
    public void Stop()
    {
        timer.Stop();
    }

    /// <summary>
    /// Gets a value indicating whether this instance is running.
    /// </summary>
    /// <value>
    /// <c>true</c> if this instance is running; otherwise, <c>false</c>.
    /// </value>
    public bool IsRunning
    {
        get
        {
            return timer.Enabled;
        }
    }

    /// <summary>
    /// Gets the ticks total.
    /// </summary>
    /// <returns>
    /// The ticks.
    /// </returns>
    public int GetTicksTotal()
    {
        return ticks;
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
        timer.Dispose();
    }
}

Safety first

Classes have many (inherited) methods. There are some methods you most likely never ever want to expose (like the Dispose method). That's why the Interpertation is always done on an interface. This is especially important when you are using this project as part of RPC construction.

To execute JSON commands we'll need to create an IInterpreter based on an instance of the timer and the IRemoteTimer interface:

var json = "";
var timer = new RemoteTimer(10);
var interpretor = Interpretation.Create<IRemoteTimer>(timer);

Executing a method

A method is defined as a JSON object with a method-name property. The system uses it to map it to a method.

/* Start the timer and assert it is running */
json = @"{ ""method-name"": ""Start"" }";
interpretor.Execute(json);

Assert.IsTrue(timer.IsRunning);

Got parameters?

Parameters are defined as properties as well. Just add their names and values to JSON:

/* Set a new interval: */
json = @"{ ""method-name"": ""SetInterval"", ""newInterval"": 20 }";

interpretor.Execute(json);
Assert.AreEqual(20, timer.Interval);

A series of methods

If you want to execute more than one method, just add the command in an array:

/* Stop the timer, set a new interval and restart it: */
json = @"[{ ""method-name"": ""Stop""},
          { ""method-name"": ""SetInterval"", ""newInterval"": 20 },
          { ""method-name"": ""Start""}]";

interpretor.Execute(json);
Assert.AreEqual(20, timer.Interval);

How about retrieving information?

Thought of that as well! Any results are returned as an object or an array of objects (in case of a series):

/* Get tick information from timer */
json = @"{ ""method-name"": ""GetTicksTotal""}";
var ticks = interpretor.Execute(json);

Assert.IsNotNull(ticks);
Assert.IsInstanceOfType(ticks, typeof(int));
Assert.IsTrue(Convert.ToInt32(ticks) > 0);

How about extension methods?

Extension methods are awesome. Let's define a Pause method:

public static class IRemoteTimerExtensions
{
    /// <summary>
    /// Pauses the specified timer.
    /// </summary>
    /// <param name = "timer">The timer.</param>
    public static void Pause(this IRemoteTimer timer)
    {
        if (timer.IsRunning)
        {
            timer.Stop();
        }
        else
        {
            timer.Start();
        }
    }
}

To allow extensions methods to be considered, they'll need to be given to the Create:

/* Pause the timer twice and assert running state */

var json = "";
var timer = new RemoteTimer(10);
var interpretor = Interpretation.Create<IRemoteTimer>(
    timer, 
    typeof(IRemoteTimerExtensions)
);

timer.Start(); 

json = @"{ ""method-name"": ""Pause"" }";
interpretor.Execute(json);
Assert.IsFalse(timer.IsRunning);

json = @"{ ""method-name"": ""Pause"" }";
interpretor.Execute(json);
Assert.IsTrue(timer.IsRunning);

Wrap up

The code has potential to solve a host of problems. If you like to fork it, please check GitHub and let me know what you think. The code used in the example can be found here.

expand_less