1

I'd like to expose a type safe API over a particular JSON format. Here is basically what I have so far:

public class JsonDictionary
{
    Dictionary<string, Type> _keyTypes = new Dictionary<string, Type>  {
        { "FOO", typeof(int) },
        { "BAR", typeof(string) },
        };
    IDictionary<string, object> _data;
    public JsonDictionary()
    {
        _data = new Dictionary<string, object>();
    }
    public void Set<T>(string key, T obj)
    {
        if (typeof(T) != _keyTypes[key])
            throw new Exception($"Invalid type: {typeof(T)} vs {_keyTypes[key]}");
        _data[key] = obj;
    }

    public dynamic Get(string key)
    {
        var value = _data[key];
        if (value.GetType() != _keyTypes[key])
            throw new Exception($"Invalid type: {value.GetType()} vs {_keyTypes[key]}");
        return value;
    }
}

Which can be used nicely as:

JsonDictionary d = new JsonDictionary();
d.Set("FOO", 42);
d.Set("BAR", "value");

However reading the value is a little disapointing and rely on late binding:

var i = d.Get("FOO");
var s = d.Get("BAR");
Assert.Equal(42, i);
Assert.Equal("value", s);

Is there some C# magic that I can use to implement a type-safe generic Get<T> instead of relying on dynamic here (ideally type should be checked at compilation time) ? I'd like to also use the pattern for Set<T> so that d.Set("BAR", 56); triggers a compilation warning.


Dictionary<string, Type> _keyTypes can be made static if needed. The above is just work in progress.

malat
  • 12,152
  • 13
  • 89
  • 158
  • What type would `d.Get(new Random().NextDouble() > 0.5 ? "FOO" : "BAR")` return? – Sweeper Aug 27 '21 at 07:18
  • It should equal `typeof(_keyTypes[key])` in this case – malat Aug 27 '21 at 07:21
  • But the compiler must know the type of that expression at compile time though (if you want compile-time type checking). `typeof(_keyTypes[key])` is not something that the compiler can know. The check has to be done at runtime, but it doesn't have to be `dynamic`. Have you considered doing something similar to what you did with `Set`? – Sweeper Aug 27 '21 at 07:29
  • Using C++ metaprogramming you can derive a `type` from an `enum`, isn't it possible in C# ? – malat Aug 27 '21 at 07:37
  • No. C# is not C++. – Sweeper Aug 27 '21 at 07:42

2 Answers2

4

I was using solution similar to this:

public class JsonDictionary
{
    public static readonly Key<int> Foo = new Key<int> { Name = "FOO" };
    public static readonly Key<string> Bar = new Key<string> { Name = "BAR" };
        
    IDictionary<string, object> _data;
    public JsonDictionary()
    {
        _data = new Dictionary<string, object>();
    }
    
    public void Set<T>(Key<T> key, T obj)
    {
        _data[key.Name] = obj;
    }

    public T Get<T>(Key<T> key)
    {
        return (T)_data[key.Name];
    }
    
    public sealed class Key<T>
    {
        public string Name { get; init; }
    }
}
malat
  • 12,152
  • 13
  • 89
  • 158
Maku
  • 1,464
  • 11
  • 20
-1

I would convert T to object and check object type with .GetType()

public class JsonDictionary
    {
        Dictionary<string, Type> _keyTypes = new Dictionary<string, Type>  {
        { "FOO", typeof(int) },
        { "BAR", typeof(string) },
        };
        IDictionary<string, object> _data;
        public JsonDictionary()
        {
            _data = new Dictionary<string, object>();
        }
        public void Set(string key, object obj)
        {
            if (obj.GetType() != _keyTypes[key])
                throw new Exception($"Invalid type: {obj.GetType()} vs {_keyTypes[key]}");
            _data[key] = obj;
        }

        public object Get(string key)
        {
            return _data[key];
        }
    }

I tried it like this and it worked:

JsonDictionary d = new JsonDictionary();
d.Set("FOO", 42);
d.Set("BAR", "value");

var i = d.Get("FOO");
var s = d.Get("BAR");
Console.WriteLine(i);
Console.WriteLine(s);

But to be honest, I don't like what you are trying to achieve.