How to create a blank class object that receives data of changing structure?

MontanaMan

Member
Joined
Feb 4, 2022
Messages
8
Programming Experience
5-10
Hello:

I'm downloading APIs and deserializing their JSON content into an object. The JSON structure varies dramatically from day to day. It also has arrays[] of information. So I need an object that can accept varying data structures.

This works:

C#:
var MyObject = JsonConvert.DeserializeObject<dynamic>(JsonString);  // This works just fine

The problem is ... the "var" declaration can only be used locally. I need to create a separate class object that is public in nature and can accept the changing structure of the JSON data.

How can I create a new public class file that will function like the "MyObject" created with the "var" declaration above?

Thanks!!
 
Last edited:
var does not stand for variant. It just a way to tell the compiler to make the variable being declared to the type of the object on the right hand side of the initialization. In the case above, the type of MyVariable will elbe dynamic. So you can declare a class member variable as dynamic. Now if you want to create a class that acts the same way as a true variant, consider not creating your own class, but instead use the Expando class instead. It acts like a variant.
 
var does not stand for variant. It just a way to tell the compiler to make the variable being declared to the type of the object on the right hand side of the initialization. In the case above, the type of MyVariable will elbe dynamic. So you can declare a class member variable as dynamic. Now if you want to create a class that acts the same way as a true variant, consider not creating your own class, but instead use the Expando class instead. It acts like a variant.
Hi: Thanks for your reply. I've edited my original post to not misuse the word "variant".

I've tried using "ExpandoObject":

C#:
dynamic MyObject = new System.Dynamic.ExpandoObject();

MyObject = JsonConvert.DeserializeObject<dynamic>(body.JsonData);

When I run this, I get a runtime error saying: "Cannot apply indexing with [] to an expression of type System.Dynamic.ExpandoObject"

There's probably something simple that I'm missing here.

The "var" declaration in my OP works very well ... I just need a class to replace that.

Would you be willing to show me a sample of an Expando Class that can accept various types of structures, including arrays[] of data?

I'm still learning, and your help is much appreciated.

Thanks!
 
Last edited:
Where are you using the indexing in the code that you presented in post #3?
 
You not doing any indexing there. You are just declaring a string. Again where is the code that is doing the indexing that gave you that exception?

For example this works without any exceptions because I'm not doing any indexing:
C#:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;

namespace SimpleCSCore
{
    class Program
    {
        static void Main() => new Program().Run();

        dynamic MyObject = new System.Dynamic.ExpandoObject();

        void Run()
        {
            MyObject = JsonConvert.DeserializeObject<dynamic>(Body.JsonData);
        }
    }

    class Body
    {
        public static string JsonData = "{number:1000, str:'string', array: [1,2,3,4,5,6]}";
    }
}
 
Replacing the Run() above with this also works without any issues:
C#:
void Run()
{
    MyObject = JsonConvert.DeserializeObject<dynamic>(Body.JsonData);

    var array = MyObject["array"];
    for(int i = 0; i < 6; i++)
        Console.WriteLine(array[i]);
}
 
Also as another quick aside, there is no need to do this:
C#:
dynamic MyObject = new System.Dynamic.ExpandoObject();
since the ExpandoObject that you are referencing with MyObject is quickly ignored and you start referencing the object returned by the deserializer when this line of code executes:
C#:
MyObject = JsonConvert.DeserializeObject<dynamic>(Body.JsonData);

Line 16 could just be written as:
C#:
dynamic MyObject;
 
Thank you for your patient responses. Using your answers and the help of others, I was able to structure the program the way that I had planned:

C#:
public class Foo
{
     public string JsonString { get; set; }
     public dynamic MyObject { get; set; }
     public string MyString1 { get; set; }
     public string MyString2 { get; set; }
}

After creating the "Foo" class described above, I could set and get the "MyObject" property from a different class like this:
C#:
public class DifferentClass
{
    //The Foo class is already instantiated with the name "foo".  The ".JsonString" property is already loaded with JSON data.
     public static MyMethod()
     {
          foo.MyObject = JsonConvert.DeserializeObject<dynamic>(foo.JsonString);

          foo.MyString1 = foo.MyObject[0].DataTree.Location1;
          foo.MyString2 = foo.MyObject[0].DataTree.Location2;
     }
}

Note that the only reason for the [0] brackets is because the JSON data had arrays in it.

Again ... thanks for taking the time to help. When I posted the original question , I had been coding all day and all night and just wasn't thinking very clearly!! LOL
 
I don't understand. If you said that the shape of the objects change all the time, then how do you know that the top level will always be an array, and that the first element will have a "DataTree" member and that element will always have "Location1" and "Location2" present?

If you know those to always be true, then there is no need to use dynamic. You would either use deserializing to classes, or LINQ for JSON, or combination of both:
C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;

namespace SimpleCSCore
{
    class Record
    {
        [JsonProperty(PropertyName = "DataTree")]
        public Foo Data { get; set; }
    }

    class Foo
    {
        [JsonProperty(PropertyName = "Location1")]
        public string MyString1 { get; set; }

        [JsonProperty(PropertyName = "Location2")]
        public string MyString2 { get; set; }

        public override string ToString()
            => $"{{ MyString1 = \"{MyString1}\", MyString2 = \"{MyString2}\" }}";
    }

    class Program
    {
        static void Main() => new Program().Run();

        static string JsonString =
@"
[
    {
        Name : 'Malcolm Reynolds',
        DataTree : {
            Location0 : 'Captain',
            Location1 : 'Shadow',
            Location2 : 'Independents',
            Location3 : 'Serenity'
        }
    },
    {
        FirstName : 'Inara',
        LastName : 'Serra',
        DataTree : {
            Location0 : 'Companion',
            Location1: 'Shihnon',
            Location2: 'House Madrassa',
        }
    }
]
";

        Foo UseClasses()
        {
            var records = JsonConvert.DeserializeObject<Record[]>(JsonString);
            return records[0].Data;
        }

        Foo UseLinqForJson()
        {
            var records = JArray.Parse(JsonString);
            var dataTree = records[0].SelectToken("DataTree");
            return new Foo
            {
                MyString1 = dataTree["Location1"].Value<string>(),
                MyString2 = dataTree["Location2"].Value<string>(),
            };
        }

        Foo UseLinqForJsonAndClasses()
        {
            var records = JArray.Parse(JsonString);
            return records[0].SelectToken("DataTree").ToObject<Foo>();
        }

        void Run()
        {
            Console.WriteLine(UseClasses());
            Console.WriteLine(UseLinqForJson());
            Console.WriteLine(UseLinqForJsonAndClasses());
        }
    }
}

On NetFiddle: C# Online Compiler | .NET Fiddle
 
Last edited:
Sorry for not giving more explanation. The API JSON data will change between 6 set options, and it is easy to programmatically determine which of those options it is. I didn't want to get into the details of that, because that was not what I was having difficulty with. I had been coding all night, and wasn't careful enough in the wording of my question. I'll try to do better in the future.

The "Location1", "Location2", etc. was just a simple example to show how the dynamic property would be used, not my actual data structure.

Thanks again for all of your help ... it's working very well now.
 
Ah. I see. So it is changing shape only because you are forcing all the parsed JSON to be in MyObject, and then you need to retrieve data from MyObject. If you had separate MyObjectFromOption1, MyObjectFromOption2, etc. then you wouldn't need dynamic, but would place you in the difficult situation of needing 6 different versions to maintain. I agree, that can be painful.

I assume you looked at using the Strategy Pattern and considered it to be overkill for what you need?
 
Back
Top Bottom