7

I am trying to create a configuration file using Json that will hold configuration for various types of objects.

Consider this file:

{
    "cameras": [
        {
            "type": "Some.Namespace.CameraClass",
            "assembly": "Some.Assembly",
            "configuration": {
                "ip": "127.0.0.1",
                "port": 8080
            }
        }
    ]
}

At runtime I will use the two "type" and "assembly" properties to construct an object supporting a specific interface, and then I would like to load the configuration into that object.

However, at compile time I do not know the type that "configuration" would map to. I would like to retain it as a json "property" and feed that into the camera object, and then let that object deserialize the json into the right type.

As such I would like to just "carry" the part of the configuration file containing configuration for a particular camera type with me into the object itself, and let it deal with it, treating it like a black box while I carry it like that. The structure of that part should be preserved since I would like full fidelity when creating the configuration types for each camera implementation, even adding subobjects if necessary.

For this particular camera I would configure an IP address and a port, for some other camera I would require authorization data, and for some other camera something completely different.

I would like for the property that would hold this configuration to just get the Json directly, still as a string.

Is this possible?

Here is a LINQPad example that has some bits commented out:

void Main()
{
    const string configurationFile = @"[
    {
        ""type"": ""UserQuery+Camera1"",
        ""configuration"": { ""id"": 10 }
    },
    {
        ""type"": ""UserQuery+Camera2"",
        ""configuration"": { ""name"": ""The second camera"" }
    }
]";
    var cameras = JsonConvert.DeserializeObject<Camera[]>(configurationFile);
    foreach (var camera in cameras)
    {
        var type = Type.GetType(camera.Type);
        var instance = Activator.CreateInstance(type, new object[0]) as ICamera;
        // instance.Configure(camera.Configuration);
    }
}

public class Camera
{
    public string Type { get; set; }
    public JObject Configuration { get; set; }
}

public interface ICamera
{
    void Configure(string json);
}

public class Camera1 : ICamera
{
    private class Configuration
    {
        public int Id { get; set; }
    }

    public void Configure(string json)
    {
        JsonConvert.DeserializeObject<Configuration>(json).Dump();
    }
}

public class Camera2 : ICamera
{
    private class Configuration
    {
        public string Name { get; set; }
    }

    public void Configure(string json)
    {
        JsonConvert.DeserializeObject<Configuration>(json).Dump();
    }
}

The two commented out bits, namely the property in the Camera class, and the call to the Configure method, is what I'd like working.

Is there something I can tag that property with, or some other type I can pick for that property, that would make this work?

I know I can make the property dynamic, which would stuff a JObject into it, but then each Configure method of each camera implementation would have to deal with a JObject and not a known non-dynamic type.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • In what way would you have static typing with your current approach? You don't have any properties of `ip` and `port`, or indeed any class representing the configuration anyway. You *could* potentially try making the property type `JObject`, but again you wouldn't have intellisense. I'm not sure how you're expecting to get Intellisense for this at all... – Jon Skeet Jan 12 '15 at 22:03
  • Inside the specific type being loaded I would take the Json of the configuration and deserialize that into a privately known configuration object. In other words, the external code loading the configuration file would consider this a black box, but the actual camera implementation would be able to deserialize this to a known type. – Lasse V. Karlsen Jan 12 '15 at 22:04
  • But `Camera.Configuration` wouldn't be of the concrete type at compile-time, so how would you expect Intellisense to work? If I type `camera.Configuration.` what properties would you expect to see? As I say, it's possible that JObject would give you what you want, but I haven't tried it. – Jon Skeet Jan 12 '15 at 22:06
  • I've appended my hypothetical configuration method. – Lasse V. Karlsen Jan 12 '15 at 22:06
  • Right, but anyone just with a reference to `Camera` is none the wiser - so you still haven't got static typing or Intellisense *there*. Perhaps that's not what you were worried about though? It's not really clear. – Jon Skeet Jan 12 '15 at 22:07
  • I know my question wasn't clear. The whole point is that the configuration loader code, the one that reads the configuration file, knows nothing about the internals of each specific camera implementation. The configuration property, for this part of the code, would be a string, a JsonProperty, JObject, something that is just a black box. However, once this code has constructed a particular camera instance, this opaque configuration json is fed to that object, and it would then deserialize the json into its own privately known configuration type. – Lasse V. Karlsen Jan 12 '15 at 22:07
  • The outside world knows nothing about how each camera is intended to be configured, but I would still like this outside world to read the configuration file and carry the part of that file containing the configuration for a particular camera to an instance of that camera. – Lasse V. Karlsen Jan 12 '15 at 22:08
  • The outside world will not use the configuration for anything, I would just like to feed the part of the configuration file containing the configuration for a particular camera to an instance of that camera, and have it deal with it properly. – Lasse V. Karlsen Jan 12 '15 at 22:09
  • I will edit the question and make a fully workable example. – Lasse V. Karlsen Jan 12 '15 at 22:09
  • 1
    Why not store the whole camera configuration as a string in the top-level JSON file. And this string happens to be valid JSON when deserialized by the camera... – DrKoch Jan 12 '15 at 22:10
  • I would like to avoid encapsulating json inside a string property, though this would work, but that would mean I have to carefully escape all quotes and such. – Lasse V. Karlsen Jan 12 '15 at 22:13

2 Answers2

9

It looks like if you use a property of type JObject, that parses but preserves the JSON:

using System;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;    

public class Foo
{
    public string Name { get; set; }
    public int Age { get; set; }
    public JObject Configuration { get; set; }
}

public class Test
{
   public static void Main()
   {
       var json = File.ReadAllText("test.json");
       var foo = JsonConvert.DeserializeObject<Foo>(json);
       Console.WriteLine(foo.Configuration);
   }

}

Test.json:

{
  "name": "Jon",
  "age": 10,
  "configuration": {
    "ip": "127.0.0.1",
    "port": 8080
  }
}

Output:

{
  "ip": "127.0.0.1",
  "port": 8080
}

I suspect you can deserialize straight from the JObject, but you can always convert it back to a string if you really want to.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I didn't think of serializing it back to a string. This is application startup, there will be 1-5 cameras typically, one level extra of json magic is not a problem, this seems like a workable solution. Please take a look at the LINQPad example I appended to my question and see if that sparks some new ideas but this seems really good. – Lasse V. Karlsen Jan 12 '15 at 22:19
  • In my example, the call to `Configure` is now this: `instance.Configure(JsonConvert.SerializeObject(camera.Configuration));`. This seems like the best I can do for the moment. – Lasse V. Karlsen Jan 12 '15 at 22:20
  • @LasseV.Karlsen: Have you tried calling `ToObject(Type)` or `TObject` on the `JObject`? – Jon Skeet Jan 12 '15 at 22:26
  • No, I'm investigating the methods of that now and that looks really useful, seems I don't have to convert back and forth at all, though perhaps that is what is being done internally. – Lasse V. Karlsen Jan 12 '15 at 22:28
-1

If second level configuration is just a string:

{
    "cameras": [
        {
            "type": "Some.Namespace.CameraClass",
            "assembly": "Some.Assembly",
            "configuration": "{ \"ip\": \"127.0.0.1\", \"port\": 8080 }"
        }
    ]
}

This maps to a class:

public class Camera
{
    public string Type { get; set; }
    public string Assembly { get; set; }
    public string Configuration { get; set; }
}

Then you could do a second level deserialize like already shown in your question.

DrKoch
  • 9,556
  • 2
  • 34
  • 43
  • I know I can do this, but I would like to avoid all those escape characters. However, @Jon's answer gave me what I wanted. Yes, I have to take json, deserialize it, serialize a small bit of it back into a json string, then deserialize that another time inside a different class, but I can live with that. – Lasse V. Karlsen Jan 12 '15 at 22:21