How to use the JSON lib

I am trying to decode some reply from openweather which is in JSON format.

I can’t seem to find any documentation on the JSON library?

I know it’s not a substitute for documentation, but there’s a long discussion with examples here : Json Deserialized example - #68 by sytak

I also have a validation program on my other computer. I will find that and post here shortly.

Test program I found on my Windows system from looooong ago:

using System;
using System.Diagnostics;
using System.Text;

using Bytewizer.TinyCLR.DigitalPortal.Client.Models;

using GHIElectronics.TinyCLR.Data.Json;
using GHIElectronics.TinyCLR.Native;

namespace JsonUnitTest
{
    public class NetworkConfigDocument
    {
        public bool UseWifi { get; set; } = false;
        public string WifiSSID { get; set; } = string.Empty;
        public string WifiPassword { get; set; } = string.Empty;
        public bool UseDhcp { get; set; } = true;
        public string IpAddress { get; set; } = string.Empty;
        public string IpGateway { get; set; } = string.Empty;
        public string IpNetmask { get; set; } = string.Empty;
        public string DnsServers { get; set; } = string.Empty;
    }

    public interface IThingy
    {
        bool ThingyVal { get; set; }
    }

    public abstract class BaseClass
    {
        public string BaseClassProp { get; set; }
    }

    public class DerivedClassA : BaseClass, IThingy
    {
        public double DerivedClassAProp { get; set; }
        public bool ThingyVal { get; set; }
    }

    public class DerivedClassB : BaseClass, IThingy
    {
        public int DerivedClassBProp { get; set; }
        public bool ThingyVal { get; set; }
    }

    public class HostClass
    {
        public BaseClass poly1 { get; set; }
        public BaseClass poly2 { get; set; }
        public IThingy poly3 { get; set; }
        public IThingy poly4 { get; set; }
    }

    public class ChildClass
    {
        public int one;
        public int two;
        public int three;
        public int four { get; set; }
    }

    public enum Hues
    {
        red, green, blue, chartruese
    }

    public class TestClass
    {
        public int i;
        public float f;
        public double d;
        public long l;
        public ulong ul;
        public string aString;
        //public Hues hue;

        public int iProp { get; set; }
        public float fProp { get; set; }
        public double dProp { get; set; }
        public string sProp { get; set; }
        public long lProp { get; set; }
        public ulong ulProp { get; set; }
        public Hues hueProp { get; set; }

        public string someName;
        public DateTime Timestamp;
        public int[] intArray;
        public string[] stringArray;
        public ChildClass child1;
        public ChildClass Child { get; set; }
    }

    // A class used by LucaP
    public class Pager
    {
        public int ID { get; set; }
        public int RIC { get; set; }
        public string Name { get; set; }
        public string SerialNumber { get; set; }
    }

    public enum State
    {
        State0,
        State1,
        State2,
        State3
    }

    public class Data
    {
        public String Name { get; set; }
        public State CurrentState { get; set; }
    }

    class Program
    {
        static void Main()
        {
            //var serialized = JsonConverter.Serialize(new NetworkConfigDocument());
            //var json = serialized.ToString();

            int i = 123;
            ulong ul = 456ul;

            var x = (ulong)i;
            var y = (int)ul;

            Data instance = new Data();
            instance.Name = "Martin";
            instance.CurrentState = State.State2;

            var json = JsonConverter.Serialize(instance);

            var newInstance = (Data)JsonConverter.DeserializeObject(json.ToString(), typeof(Data));

            DoArrayTest();
            DoDoubleTest();
            DoDateTimeTest();
            DoSimpleObjectTest();
            DoComplexObjectTest();
            DoPolymorphismTest();
            DoLucaPTest();
            DoBytewizerTest();
        }

        public static void DoBytewizerTest()
        {
            string validJson = "{\"lat\":33.8578,\"lon\":-117.7869,\"timezone\":\"America/Los_Angeles\",\"timezone_offset\":-25200,\"current\":{\"dt\":1619231063,\"sunrise\":1619183415,\"sunset\":1619231290,\"temp\":60.08,\"feels_like\":58.95,\"pressure\":1016,\"humidity\":67,\"dew_point\":49.1,\"uvi\":0.11,\"clouds\":1,\"visibility\":10000,\"wind_speed\":9.22,\"wind_deg\":280,\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01d\"}]},\"daily\":[{\"dt\":1619204400,\"sunrise\":1619183415,\"sunset\":1619231290,\"moonrise\":1619218140,\"moonset\":1619176800,\"moon_phase\":0.37,\"temp\":{\"day\":66.33,\"min\":57.27,\"max\":67.86,\"night\":57.97,\"eve\":60.67,\"morn\":57.51},\"feels_like\":{\"day\":65.01,\"night\":56.25,\"eve\":59.5,\"morn\":56.25},\"pressure\":1016,\"humidity\":50,\"dew_point\":46.56,\"wind_speed\":11.16,\"wind_deg\":221,\"wind_gust\":5.59,\"weather\":[{\"id\":802,\"main\":\"Clouds\",\"description\":\"scattered clouds\",\"icon\":\"03d\"}],\"clouds\":40,\"pop\":0,\"uvi\":8.64},{\"dt\":1619290800,\"sunrise\":1619269748,\"sunset\":1619317737,\"moonrise\":1619308620,\"moonset\":1619265180,\"moon_phase\":0.41,\"temp\":{\"day\":67.59,\"min\":55.18,\"max\":69.93,\"night\":58.35,\"eve\":66.2,\"morn\":55.18},\"feels_like\":{\"day\":66.2,\"night\":53.89,\"eve\":64.72,\"morn\":53.89},\"pressure\":1016,\"humidity\":46,\"dew_point\":45.45,\"wind_speed\":11.3,\"wind_deg\":231,\"wind_gust\":4.74,\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01d\"}],\"clouds\":4,\"pop\":0,\"uvi\":8.44},{\"dt\":1619377200,\"sunrise\":1619356081,\"sunset\":1619404184,\"moonrise\":1619399280,\"moonset\":1619353560,\"moon_phase\":0.45,\"temp\":{\"day\":65.07,\"min\":54.43,\"max\":65.07,\"night\":57.25,\"eve\":62.26,\"morn\":54.43},\"feels_like\":{\"day\":63.43,\"night\":53.1,\"eve\":60.26,\"morn\":53.1},\"pressure\":1014,\"humidity\":46,\"dew_point\":43.2,\"wind_speed\":13.47,\"wind_deg\":231,\"wind_gust\":13.51,\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"clouds\":100,\"pop\":0.02,\"uvi\":6.37},{\"dt\":1619463600,\"sunrise\":1619442416,\"sunset\":1619490631,\"moonrise\":1619490000,\"moonset\":1619442000,\"moon_phase\":0.5,\"temp\":{\"day\":61.56,\"min\":55.62,\"max\":64.81,\"night\":55.67,\"eve\":61.97,\"morn\":55.65},\"feels_like\":{\"day\":59.49,\"night\":54.25,\"eve\":59.79,\"morn\":54.25},\"pressure\":1012,\"humidity\":44,\"dew_point\":38.79,\"wind_speed\":14.03,\"wind_deg\":223,\"wind_gust\":11.25,\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],\"clouds\":100,\"pop\":0.5,\"uvi\":6.82},{\"dt\":1619550000,\"sunrise\":1619528752,\"sunset\":1619577078,\"moonrise\":1619580840,\"moonset\":1619530620,\"moon_phase\":0.53,\"temp\":{\"day\":64.04,\"min\":52.99,\"max\":69.46,\"night\":59.88,\"eve\":68.9,\"morn\":52.99},\"feels_like\":{\"day\":62.11,\"night\":51.19,\"eve\":67.32,\"morn\":51.19},\"pressure\":1017,\"humidity\":42,\"dew_point\":39.67,\"wind_speed\":10.58,\"wind_deg\":235,\"wind_gust\":6.26,\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01d\"}],\"clouds\":2,\"pop\":0,\"uvi\":8.78},{\"dt\":1619636400,\"sunrise\":1619615089,\"sunset\":1619663525,\"moonrise\":1619671740,\"moonset\":1619619540,\"moon_phase\":0.56,\"temp\":{\"day\":77.18,\"min\":57.4,\"max\":82.58,\"night\":68.23,\"eve\":79.39,\"morn\":57.4},\"feels_like\":{\"day\":75.45,\"night\":55.65,\"eve\":79.39,\"morn\":55.65},\"pressure\":1021,\"humidity\":18,\"dew_point\":30.72,\"wind_speed\":11.12,\"wind_deg\":255,\"wind_gust\":11.81,\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01d\"}],\"clouds\":0,\"pop\":0,\"uvi\":9},{\"dt\":1619722800,\"sunrise\":1619701427,\"sunset\":1619749972,\"moonrise\":1619762520,\"moonset\":1619708760,\"moon_phase\":0.6,\"temp\":{\"day\":84.09,\"min\":64.99,\"max\":89.37,\"night\":72.1,\"eve\":87.06,\"morn\":64.99},\"feels_like\":{\"day\":81.16,\"night\":62.56,\"eve\":83.48,\"morn\":62.56},\"pressure\":1019,\"humidity\":12,\"dew_point\":26.28,\"wind_speed\":9.73,\"wind_deg\":251,\"wind_gust\":3,\"weather\":[{\"id\":801,\"main\":\"Clouds\",\"description\":\"few clouds\",\"icon\":\"02d\"}],\"clouds\":14,\"pop\":0,\"uvi\":9},{\"dt\":1619809200,\"sunrise\":1619787765,\"sunset\":1619836420,\"moonrise\":0,\"moonset\":1619798460,\"moon_phase\":0.64,\"temp\":{\"day\":83.97,\"min\":67.17,\"max\":88.29,\"night\":69.51,\"eve\":84.58,\"morn\":67.17},\"feels_like\":{\"day\":81.18,\"night\":65.34,\"eve\":81.72,\"morn\":65.34},\"pressure\":1016,\"humidity\":18,\"dew_point\":33.76,\"wind_speed\":9.64,\"wind_deg\":236,\"wind_gust\":2.75,\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear sky\",\"icon\":\"01d\"}],\"clouds\":9,\"pop\":0,\"uvi\":9}]}";
            Debug.WriteLine(validJson);  // This is valid JSON.  Checked on a couple differnt websites.

            var jsonResponse = (WeatherResponse)JsonConverter.DeserializeObject(validJson, typeof(WeatherResponse), Bytewizer_CreateInstance);
            Debug.WriteLine($"Temp: {jsonResponse.current.temp}");
        }

        private static object Bytewizer_CreateInstance(string path, JToken root, Type baseType, string name, int length)
        {
            if (path == "/" & name == "daily")
                return new Daily[length];

            else if (path == "//daily" & name == null)
                return new Daily();

            else if (path == "//daily" & name == "weather")
                return new Weather[length];

            else if (path == "//daily/weather" & name == null)
                return new Weather();

            else if (path == "/current" & name == "weather")
                return new Weather[length];

            else if (path == "/current/weather" & name == null)
                return new Weather();

            else
                return null;
        }

        public static byte[] LucaP_Serialize(object data)
        {
            JToken json = JsonConverter.Serialize(data);
            string jsonString = json.ToString();
            byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);
            return jsonBytes;
        }

        public static Pager LucaP_Deserialize(byte[] data)
        {
            if (data.Length > 0)
            {
                string byteString = Encoding.UTF8.GetString(data);
                Pager pager = (Pager)JsonConverter.DeserializeObject(byteString, typeof(Pager));
                return pager;
            }
            return null;
        }

        private static void DoLucaPTest()
        {
            var pager = new Pager
            {
                ID = 1,
                RIC = 2,
                Name = "foo",
                SerialNumber = "123",
            };

            var data = LucaP_Serialize(pager);
            var result = LucaP_Deserialize(data);

            if (result.ID != pager.ID ||
                result.RIC != pager.RIC ||
                result.Name != pager.Name ||
                result.SerialNumber != pager.SerialNumber)
            {
                throw new Exception("LucaP test failed");
            }
        }

        private static void DoPolymorphismTest()
        {
            var dca = new DerivedClassA
            {
                BaseClassProp = "dca",
                DerivedClassAProp = 3.14,
                ThingyVal = true,
            };

            var dcb = new DerivedClassB
            {
                BaseClassProp = "dcb",
                DerivedClassBProp = 12,
                ThingyVal = false,
            };

            var host = new HostClass
            {
                poly1 = dca,
                poly2 = dcb,
                poly3 = dca,
                poly4 = dcb
            };

            //
            // Instance factory test
            //
            var result = JsonConverter.Serialize(host);
            var bson = result.ToBson();
            var str = result.ToString();
            Debug.WriteLine(str);
            var compare1 = (HostClass)JsonConverter.FromBson(bson, typeof(HostClass), PolyTestCreateInstance);
            var compare2 = (HostClass)JsonConverter.DeserializeObject(str, typeof(HostClass), PolyTestCreateInstance);

            //
            // Type decoration test
            //
            result = JsonConverter.Serialize(host, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
            bson = result.ToBson();
            str = result.ToString();
            Debug.WriteLine(str);
            compare1 = (HostClass)JsonConverter.FromBson(bson, typeof(HostClass));
            compare2 = (HostClass)JsonConverter.DeserializeObject(str, typeof(HostClass));

        }

        private static object PolyTestCreateInstance(string path, JToken root, Type baseType, string name, int length)
        {
            if (path == "/poly1" || path == "/poly3")
            {
                return new DerivedClassA();
            }
            else if (path == "/poly2" || path == "/poly4")
            {
                return new DerivedClassB();
            }
            else
            {
                return null;
            }
        }

        private static void DoArrayTest()
        {
            int[] intArray = new[] { 1, 3, 5, 7, 9 };

            var result = JsonConverter.Serialize(intArray);
            var bson = result.ToBson();
            var compare = JsonConverter.FromBson(bson, typeof(int[]));
            if (!ArraysAreEqual(intArray, (Array)compare))
                throw new Exception("array test failed");
            Debug.WriteLine("Array test succeeded");
        }

        private static void DoDoubleTest()
        {
            double[] dArray = new[] { 1, 3, 5, double.NaN, 9 };

            var result = JsonConverter.Serialize(dArray);
            var bson = result.ToBson();
            var compare = JsonConverter.FromBson(bson, typeof(double[]));
            if (!ArraysAreEqual(dArray, (Array)compare))
                throw new Exception("array test failed");
            Debug.WriteLine("Array test succeeded");
        }

        public static void DoDateTimeTest()
        {
            //SystemTime.SetTime();
            var dt1 = DateTimeExtensions.FromIso8601("2021-04-16T09:54:24.0598721+02:00");
            Debug.WriteLine("Date/Time : " + dt1.ToString());
            Debug.WriteLine("Date/Time local : " + dt1.ToLocalTime().ToString());
            Debug.WriteLine("Date/Time universal : " + dt1.ToUniversalTime().ToString());
        }

        private static void DoSimpleObjectTest()
        {
            var source = new ChildClass()
            {
                one = 1,
                two = 2,
                three = 3,
                four = 4
            };

            var serialized = JsonConverter.Serialize(source);
            var bson = serialized.ToBson();
            var compare = (ChildClass)JsonConverter.FromBson(bson, typeof(ChildClass));
            if (source.one != compare.one ||
                source.two != compare.two ||
                source.three != compare.three ||
                source.four != compare.four)
                throw new Exception("simple object test failed");
            Debug.WriteLine("simple object test passed");
        }

        private static void DoComplexObjectTest()
        {
            var test = new TestClass()
            {
                aString = "A string",
                i = 10,
                f = 3.1415925f,
                d = 2 * 3.1415925,
                l = long.MaxValue,
                ul = (ulong)(ulong.MaxValue / 2.0),
//                hue = Hues.chartruese,
                iProp = 20,
                fProp = 2.1f,
                dProp = 2.2,
                sProp = "a property",
                lProp = long.MaxValue,
                ulProp = (ulong)(ulong.MaxValue / 2.0),
                hueProp = Hues.blue,
                someName = "who?",
                Timestamp = DateTime.UtcNow,
                intArray = new[] { 1, 3, 5, 7, 9 },
                stringArray = new[] { "two", "four", "six", "eight" },
                child1 = new ChildClass() { one = 1, two = 2, three = 3 },
                Child = new ChildClass() { one = 100, two = 200, three = 300 }
            };
            var result = JsonConverter.Serialize(test);
            Debug.WriteLine("Serialization:");
            var stringValue = result.ToString();
            Debug.WriteLine(stringValue);

            var dserResult = JsonConverter.Deserialize(stringValue);
            Debug.WriteLine("After deserialization:");
            Debug.WriteLine(dserResult.ToString());

            var newInstance = (TestClass)JsonConverter.DeserializeObject(stringValue, typeof(TestClass), CreateInstance);
            if (test.i != newInstance.i ||
                test.f != newInstance.f ||
                test.d != newInstance.d ||
                test.l != newInstance.l ||
                test.ul != newInstance.ul ||
//                test.hue != newInstance.hue ||
                test.iProp != newInstance.iProp ||
                test.fProp != newInstance.fProp ||
                test.dProp != newInstance.dProp ||
                test.sProp != newInstance.sProp ||
                test.lProp != newInstance.lProp ||
                test.ulProp != newInstance.ulProp ||
                test.hueProp != newInstance.hueProp ||
                test.Timestamp.ToString() != newInstance.Timestamp.ToString() ||
                test.aString != newInstance.aString ||
                test.someName != newInstance.someName ||
                !ArraysAreEqual(test.intArray, newInstance.intArray) ||
                !ArraysAreEqual(test.stringArray, newInstance.stringArray)
                )
                throw new Exception("complex object test failed");

            // bson tests
            var bson = result.ToBson();
            var compare = JsonConverter.FromBson(bson, typeof(TestClass), CreateInstance);
        }

        private static object CreateInstance(string path, JToken root, Type baseType, string name, int length)
        {
            if (name == "intArray")
                return new int[length];
            else if (name == "stringArray")
                return new string[length];
            else
                return null;
        }

        private static bool ArraysAreEqual(Array a1, Array a2)
        {
            if (a1 == null && a2 == null)
                return true;
            if (a1 == null || a2 == null)
                return false;
            if (a1.Length != a2.Length)
                return false;
            for (int i = 0; i < a1.Length; ++i)
            {
                if (!a1.GetValue(i).Equals(a2.GetValue(i)))
                    return false;
            }
            return true;
        }
    }
}
1 Like

Hi mcalsyn,

Since this just popped up again, I have a quick question on this topic:
You have a JsonSerializerSettings property of TypeNameHandling, but reviewing the source code, I can’t quite figure out what it is doing.

My understanding is that in the full framework, this option will store the type of the object as a key-value such as “$type”: “MyType” or similar in the JSON string. This allows collections of objects of mixed types to be serialized and deserialized. Can you comment on the maturity of this feature, the limitations it might have, and differences from the full framework? To my eye, it looks like it is only used when deserializing and even then there is no DeserializeObject override that doesn’t require a type parameter.

Thanks!

Hi Phil - yes, this is only implemented as a serialization option that adds “$type” properties on output. It doesn’t do anything during deserialization.

I could have used this as an optional way to avoid the use of type arguments during deserialization and helper functions for deserializing arrays. Couldn’t eliminate those completely because I still have to handle json that comes in without type hints, but yes, bottom line is that type hinting is not implemented in the deserialization paths.

I’ve almost given up on this after a few nights trying to work out how to extract data from the JSON reply from the openweathermap.org website.

How is CreateInstance used? Is it called repeatedly to parse the data as it only seems to be called once?

I need and easy to explain sample project. !!

I realize it’s not as straightforward as it ought to be. CreateInstance is called each time the deserializer needs to instantiate an object in a collection. This is necessary because unlike the larger framework, there isn’t enough type information available to do it automatically. It’s compounded by a bug in the type system for arrays dating back to the netmf days. So if you have an array of 15 items in json, CreateInstance will be called 15 times. I think it also gets called for nested (child) objects that aren’t simple strings or scalars.

I’ll see if I can put together a sample that works with data from openweathermap.org - just the deserialization part of the task. I’ll do that within the next 48 hrs.

You can find an example here : TinyCLR-Samples/Projects/Json Deserialization at master · PervasiveDigital/TinyCLR-Samples · GitHub

I used openweathermap.org to pull the current weather from Madison Heights, MI, home to our GHI friends. All I can say is Brrrrrrrrrrrr.

Anyway, this shows how to decode their current-weather api call. I’m happy to answer follow-on questions.

I have also submitted this as a PR to TinyCLR-Samples : A json deserializer example by martincalsyn · Pull Request #178 · ghi-electronics/TinyCLR-Samples · GitHub

2 Likes