0

I am using Visual Studio's auto generated REST Web Application project structure to create a RESTful API that consumes a JSON input.

Without going into too much detail, I have attempted to define a JSON structure like these:

Example 1:

"operation": {
    "type": "EXIST",
    "args": [
      {
        "ColumnName": "SomeColumnA",
        "Row": 2
      }
    ]
  }

Example 2:

"operation": {
    "type": "ADD", 
    "args": [
      {
        "type": "ADD",
        "args": [
          {
            "columnName": "SomeColumnB",
            "row": 12
          },
          {
            "columnName": "SomeColumnC",
            "row": 18
          }
        ]
      },
      20
    ]
  }

operation represents one of any number of basic database operations and the arguments for those operations. In my first example, the operation is EXIST, which should check a database cell to see if a value exists or not. The args for this operation is simply an object that contains the column and row information for the cell to check (I call this a Value). In my second example, the function is ADD, which should add two values together and return the sum. Here, the arguments are a constant of 20 and a nested ADD function, which itself takes two Values. So, in general, the args array can take either primitive values, another nested operation, or a pair of values that represents a cell to read the actual value from. The ultimate goal here is to create a general structure that would allow me to nest combinations of functions, cell values, and constants to create compound functions like Average or Sum.

In my Models folder, I have the following classes to cast my data to:

public class Instruction
{
    public Operation[] Operations { get; set; }
}

public class Operation
{
    public string Type { get; set; }
    public object[] Args { get; set; }
}

public class Value
{
    public string ColumnName { get; set; }
    public int Row { get; set; }
}

Note that the Args in Operation is of type object[].

When I call my web application by POSTing this JSON to it, C# automatically parses the JSON into objects defined in my Models folder. Say we used Example 1:

[HttpPost]
public IHttpActionResult Foo(Instruction instruction)
{
    foreach(Operation op in instruction.Operations) {
        switch (op.Type) {
            case "EXIST":
                Console.WriteLine("exist"); // works fine

                // since we got here, expect Args[0] to be type 'Value'
                var value = (Value) op.Args[0]; // InvalidCastException
                // logic for EXIST
            case "ADD":
                // logic for ADD
            // ...
        }
    }
}

It's casting Operation just fine, and I get Type out correctly. I also get Args as an object[] with a lone element in it. But if I try to cast it to Value, it refuses to cast properly.

My question after all of this is: what is the best way to achieve what it looks like I'm attempting to do here? Am I on the right track? If so, what is my error? If I'm going about this the wrong way, what is a better practice alternative? Note that since I'm using Visual Studio's out-of-the-box Web Application framework I don't seem to have access to the function that deseralizes the JSON, so I don't think I can build a custom deserializer.

dbc
  • 104,963
  • 20
  • 228
  • 340
Fawfulcopter
  • 381
  • 1
  • 7
  • 15
  • Unless you are trying to conform to some already-published JSON payload spec, why not start with defining your classes first and use one of the many class-to-JSON preview tools? That way you know there more than likely won't be any problem during de-serialization. It's the same with XML, I'm lazy and always create the class first –  Apr 03 '17 at 02:39
  • Which JSON array,you want to casting?Example1 is different from Example2 – anis programmer Apr 03 '17 at 03:50
  • What happens if you define Args as `public Value[] Args{get;set;}`? Also look into the dynamic type in C#. You can define Args in your Operation model as `public dynamic Args{get;set;}`. From there you can just use `Args[0].row` without the need of another model. – garethb Apr 03 '17 at 04:21
  • @garethb - I can't set the type to `Value[]` because the array could potentially hold `int`, `string`, `double`, `bool`, `Value`, or `Operation`. I can't know ahead of time which type it is, I can only check it during runtime and perform the proper cast. – Fawfulcopter Apr 03 '17 at 04:38
  • @Fawfulcopter In this case, I would define Args as dynamic. – garethb Apr 03 '17 at 04:57

1 Answers1

2

See this fiddle that shows how I would use the dynamic type.

Fiddle

public static void Main()
{
    var json = @"{""operation"": {
        ""type"": ""ADD"",
        ""args"": [
            {
                ""type"": ""ADD"",
                ""args"": [
                    {
                        ""columnName"": ""SomeColumnB"",
                        ""row"": 12
                    },
                    {
                        ""columnName"": ""SomeColumnC"",
                        ""row"": 18
                    }
                ]
            }
        ]
    }}";
    dynamic data = JsonConvert.DeserializeObject(json);

    Console.WriteLine(data.operation.type.ToString());
    Console.WriteLine(data.operation.args[0].args[0].columnName.ToString());
    Console.WriteLine((int)data.operation.args[0].args[0].row);
}
garethb
  • 3,951
  • 6
  • 32
  • 52
  • I have to confess that I abstracted a bit of my code to try and focus on the exact problem; I edited my question with a more accurate representation of what the code looks like. Basically, my JSON will have some arbitrary number of `Operation`s and each one will be different. On top of that, for operations like `ADD`, the possible `Args` could be an `Operation`, a `Value`, or a primitive. So I guess I would have to do `try { var colName = op.args[0].columnName; } catch {}` to check for `Value`, and so forth? It'll work, but I wonder if there's a better answer. – Fawfulcopter Apr 03 '17 at 06:33
  • You can check if args is a json array or a json object. I'm not sure of the syntax off the top of my head though. Write a function to do the check so you can test any number of args. – garethb Apr 03 '17 at 22:50