Json Deserialized example

Yup. That’s a genuine oopsie. A typo on my part.

PR is here : Fix construction of arrays that are props by martincalsyn · Pull Request #1017 · ghi-electronics/TinyCLR-Libraries · GitHub

EDIT: As a workaround, the current code should work if you change the array-type props in your model classes into public fields until RC1 is out. I have added your code to the unit test suite.

3 Likes

Great! Thanks for your help.

1 Like

@mcalsyn is our hero! Thanks

3 Likes

Thanks again for your quick response. This PR fixed the issue in RC1.

This is purely cosmetic but I noticed that the prettify version looks to include an extra space between the starting bracket “[” and first “{” brace. The non-prettify version doesn’t include this extra space.
Screenshot 2021-04-28 063806

2 Likes

Interesting. Next time I lift the hood on this code, I’ll fix that too. Thanks for reporting this.

I’m trying to read a json data stream (format is unknow) and then flatten that stream into a key value pair hashtable. I can read the data into a JToken by using the JsonConverter.Deserialize(input) but then I’m struggling to figure out a way to recursively loop through the JToken to work with each element.

Any high-level advice about how I might be able to approach this would be appreciated.

Json Format

{
  "object1": {
    "property2":[2,3,4,5,6,7],
    "property1":"value1"
  },
  "key2":"value2",
  "key1":1
}

Flattened Key Value Pair

key1 = 1
key2 = value2
object1:property2:4 = 6
object1:property2:5 = 7
object1:property2:2 = 4
object1:property2:1 = 3
object1:property2:0 = 2
object1:property2:3 = 5
object1:property1 = value1

All types (JObject, JProperty, JValue, JArray) are descendants of JToken. That is, they all have JToken as their base class. So, you would have to do some ‘is’ tests to decide when you need to descend recursively or unroll an array.

So if you iterate over nodes to get JTokens, then if (node is JObject) then iterate over the JProperty values within it. If the .Value of a JProperty is another JObject, then descend recursively again. If (node is JArray) then unroll the array of JTokens. If (node is JProperty), then the .Value is your scalar or string value or another array or object to be recursed upon.

There is code in the JToken descendant implementations of .ToString() methods in the library that does this type of recursive descent for the purpose of converting a tree of JTokens into string representations. I would start with that as an example.

@mcalsyn Thank you for your quick response. With your guidance I was able to do exactly what I was looking to do. :smiley:

I do have a couple of follow-up questions.

  1. Looking at the Json Deserialize code it looks like the ‘JsonConverter.Deserialize()’ can return ‘JObject’ or an ‘JArray’ object. I could not figure out what in the Json string would make it return a ‘JArray’ as the top level object. Right now I’m assuming it will only return a ‘JObject’ in the root of the object. Is this a safe assumption?
  2. I notice that string values have quotes ("") around them. Can I remove these with JsonConvert somehow? If not, I can build a function to detect and parse them out.
  3. Any other feedback is welcome.

Output

object1:property2:1 = 3
object1:property2:5 = 7
object1:property1 = "value1"
object1:property2:0 = 2
object1:property2:4 = 6
object1:property2:3 = 5
object1:property2:2 = 4
key2 = "value2"
key1 = 1

Code

internal sealed class JsonConfigurationParser
{
    private readonly Hashtable _data = new Hashtable();

    public static Hashtable Parse(Stream input)
        => new JsonConfigurationParser().ParseStream(input);

    private Hashtable ParseStream(Stream input)
    {
        try
        {
            JToken token = JsonConverter.Deserialize(input);

            if (token is JObject jobj)
            {
                foreach (JProperty member in jobj.Members)
                {                        
                    foreach (DictionaryEntry pair in GetNode(member))
                    {
                        _data.Add(
                                pair.Key.ToString().ToLower(), 
                                pair.Value.ToString().Trim('"').TrimEnd('"')
                            );
                    }
                }
            }

            if (token is JArray jarray)
            {
                for (int i = 0; i < jarray.Length; i++)
                {
                    var property = new JProperty($"root{i}", jarray[i]);
                    
                    foreach (DictionaryEntry pair in GetNode(property))
                    {
                        _data.Add(
                                pair.Key.ToString().ToLower(),
                                pair.Value.ToString().Trim('"').TrimEnd('"')
                            );
                    }
                }
            }
        }
        catch
        {
            throw new FormatException();
        }

        return _data;
    }

    private static Hashtable GetNode(JProperty node)
    {
        var items = new Hashtable();

        if (node.Value is JObject jobj)
        {
            foreach (JProperty member in jobj.Members)
            {
                var key = string.Concat(node.Name, ":", member.Name);
                var values = GetNode(new JProperty(key, member.Value));

                foreach (DictionaryEntry pair in values)
                {
                    items.Add(pair.Key, pair.Value);
                }
            }

            return items;
        }

        if (node.Value is JArray jarray)
        {
            for (int i = 0; i < jarray.Length; i++)
            {
                var key = string.Concat(node.Name, ":", i);
                var values = GetNode(new JProperty(key, jarray[i]));

                foreach (DictionaryEntry pair in values)
                {
                    items.Add(pair.Key, pair.Value);
                }
            }

            return items;
        }

        items.Add(node.Name, node.Value);

        return items;
    }
}
1 Like

No, it’s actually not. Files containing top-level arrays or [1,2,3] or [{},{},{}] are all valid json.

Unfortunately no. I could add an option for that. For now, you’ll have to do the trimming yourself.

Overall, just by inspection, and given the comment above about top-level arrays, this looks like a good approach.

@mcalsyn Thanks! The top level array makes total sense. I get it now. I really appreciate your help it was just what I needed to figure it out. I did update the code above as a working reference example.

2 Likes