0

I am trying to deserialise JSON files that have been created by a third party tool.

The files contain a property that can look like the below:

"RECIPE": [{
        "ctPoint_0": {
            "endTemperature": 25,
            "hours": 0.07999999821186066,
            "startTemperature": 25
        },
        "ctPoint_1": {
            "endTemperature": 30,
            "hours": 0.07999999821186066,
            "startTemperature": 25
        },
        "ctPoint_2": {
            "endTemperature": 25,
            "hours": 0.07999999821186066,
            "startTemperature": 30
        },
        "pointCount": 3,
        "type": "CyclicTemp"
    }, {
        "cycles": 2,
        "type": "Repeat"
    }, {
        "cycles": 1,
        "duration": {
            "days": 0,
            "hours": 0
        },

    }]

I am using system.text.json to deserialise:

newProgram = JsonSerializer.Deserialize<Program>(jsonString, options);

Extract of my Program class:

public class Program
    {

        public IList<Recipe> RECIPE { get; set; }
    }

Recipe and cyclic_data struct:

public struct Cyclic_Data
    {
        public float endTemperature { get; set; }
        public float hours { get; set; }
        public float startTemperature { get; set; }

    }

    public struct Recipe
    {
        public int cycles { get; set; }

        // Would like to use this
        public IList<Cyclic_Data> ctPoints { get; set; }

        // this deserialises ok but obviously only to hardcoded limit
        public Cyclic_Data ctPoint_0 { get; set; }
        public Cyclic_Data ctPoint_1 { get; set; }
        public Cyclic_Data ctPoint_2 { get; set; }
        public Cyclic_Data ctPoint_3 { get; set; }
        public Cyclic_Data ctPoint_4 { get; set; }
        public Cyclic_Data ctPoint_5 { get; set; }
        public Cyclic_Data ctPoint_6 { get; set; }
        public Cyclic_Data ctPoint_7 { get; set; }
        public Cyclic_Data ctPoint_8 { get; set; }
        public Cyclic_Data ctPoint_9 { get; set; }
        public Cyclic_Data ctPoint_10 { get; set; }


        public Duration duration { get; set;}
        public string type { get; set; }
        public float temperature { get; set; }
        public int pointCount { get; set; }

    }

As per the comments, if I have a number of discrete variables of type Cyclic_Data e.g. ctPoint_0 then this successfully deserialises, however as this list could theoretically be arbitrarily large it would be a nonsense to try to declare all possible property names.

I would really like to use an IList to read in all ctPoint_X values but am struggling to find a way to do so. I was looking at the newton soft implementation instead and wondering whether [JsonProperty("name")] could be used with RegEx to solve this but could not find any successful example done in this way.

How can I deserialise this in a sensible manner?

EDIT: I am currently looking at a custom JsonNamingPolicy to rename any property name matching a RegEx "^ctPoint_[0-9]+$" to ctPoints, will let you know if this succeeds, please comment if this is doomed to fail or if there is a better way..

EDIT 2: I tried the method outlined above but it didn't work as the correct JSON for a list of items doesn't have the name at beginning of each item, however this started me thinking about the problem differently. What I ended up doing was some simple string replacements before the deserialisation. This worked fine :)

         int location;
            location = newText.IndexOf("\"ctPoint_0\":");

            newText = newText.Replace("\"ctPoint_0\":", "\"ctPoints\": [");
            if (location > 0)
            { int lastloc;

                for (int i = 1; i < 999999; i++)
                {
                    string nextString = "\"ctPoint_" + i.ToString() + "\": ";
                    lastloc = location;
                    location = newText.IndexOf(nextString);

                    newText = newText.Replace(nextString, "");
                    if (location == -1)
                    {
                        location = newText.IndexOf("}", lastloc);
                        newText = newText.Insert(location+1, "]");
                        break;
                    }
                }
               }

Thanks

dbc
  • 104,963
  • 20
  • 228
  • 340
Dave
  • 142
  • 7
  • change your backend to return list – Selvin Feb 12 '20 at 16:14
  • As I mentioned in the question and code this is what I want to do but am unable to get this to work as the property name is different for each element of the list. – Dave Feb 12 '20 at 16:20
  • Have you considered sticking with Json.NET in this case? Your JSON consists of an array of polymorphic objects with no clear type discriminator, and one of the objects consists of known and unknown properties where the unknown properties have a known schema. You want to map all that to a single,flat object. You are going to need to write one (or more) elaborate `JsonConverter` types to do that, but the API to do that is going to be substantially enhanced in System.Text.Json 5.0 due out by the end of the year, see https://github.com/dotnet/runtime/issues/1562#issue-547760443 – dbc Feb 12 '20 at 18:42
  • I am absolutely happy to stick with JSON.net I just want the best solution. I have a pretty good understanding of the schema and can access the source code that generates the files, I'm just not allowed to change it – Dave Feb 12 '20 at 19:19
  • @dbc So do you believe there is a solution using the Newton soft JSON.net? As I said in the question I looked at this but could not find an example that looked like it would definitely do what I needed. – Dave Feb 12 '20 at 19:31
  • You're going to have to write a complex converter either way, but writing complex converters is easier in Json.NET than it is in `System.Text.Json`. – dbc Feb 12 '20 at 21:50

1 Answers1

0

You can try Dictionary<string, object> and check the object type during runtime

Here are the models required for this scenario

public class CtPoint
{
    public int endTemperature { get; set; }
    public double hours { get; set; }
    public int startTemperature { get; set; }
}

public class Duration
{
    public int days { get; set; }
    public int hours { get; set; }
}

Here is the sample code to Deserialize the JSON using System.Text.Json.JsonSerializer

var results = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string,object>>>(json);

foreach (var model in results)
{
    foreach(var item in model)
    {
        if (item.Key.Contains("ctPoint"))
        {
            var ctPoint = System.Text.Json.JsonSerializer.Deserialize<CtPoint>(item.Value.ToString());
            Console.WriteLine($"{item.Key}- {ctPoint.hours} {ctPoint.startTemperature} {ctPoint.endTemperature}");
        }
        else if (item.Key.Contains("duration"))
        {
            var duration = System.Text.Json.JsonSerializer.Deserialize<Duration>(item.Value.ToString());
            Console.WriteLine($"{item.Key}- {duration.days} {duration.hours}");
        }
        else
        {
            Console.WriteLine($"{item.Key}- {item.Value.ToString()}");
        }
    }
}

Output

ctPoint_0- 0,0799999982118607 25 25
ctPoint_1- 0,0799999982118607 25 30
ctPoint_2- 0,0799999982118607 30 25
pointCount- 3
type- CyclicTemp
cycles- 2
type- Repeat
cycles- 1
duration- 0 0
Krishna Varma
  • 4,238
  • 2
  • 10
  • 25