c# .net core json

Parse A Json String With Unpredictable-Typed Fields

Matt 2020/06/15 15:31:43
25190

It's a common way to exchange data between one service and another by delivering json string.

So, this example will show some methods about implementation for transforming a json string to an object (aka. deserialize).

Let us dive into it.

Basic Usage

Here is an example,

        // we use a datetime formating string following ISO 8601-1:2019
        const string sampleJson = "{\"DXIDS\":[{\"DXID\":\"123\"}],\"LICSNO\":\"**X-1234\",\"RTPTML\":10001,\"SRVNM\":\"服務人員\",\"GVCARDT\":\"2020-05-14T14:00:00+08:00\",\"RTPTDT\":\"20200514\"}";

Create a POCO model for mapping.

    internal class Sample
    {
        public IEnumerable<Dxid> DXIDS { get; set; }
        public string LICSNO { get; set; }
        public int RTPTML { get; set; }
        public string SRVNM { get; set; }
        public DateTime GVCARDT { get; set; }
        public string RTPTDT { get; set; }
    }
    internal class Dxid
    {
        public string DXID { get; set; }
    }

Using a Deserializer to parse jsong string, and map to a specific object, System.Text.Json, and Newtonsoft.Json both can do that well.

    private static void BasicWay()
    {
        // auto map properties by a POCO mapper
        var result = System.Text.Json.JsonSerializer.Deserialize<Sample>(sampleJson);
        var result2 = Newtonsoft.Json.JsonConvert.DeserializeObject<Sample>(sampleJson);
    }

img "result"

img "results"

Another way, parse without a POCO model

Is there another way to parse json string ? Yes, sure, Newtonsoft.Json support Dynamic JSON Parsing to help us read json value.

    private static void ParseWithoutAPoco()
    {
        // don't forget to specify **dynamic**, if using a *var*, that won't work (did you know why ?)
        // for this example, we can also try **dynamic data = JObject.Parse(sampleJson);**, because the sampleJson is exectly an {} Json object
        // while the sampleJson is exectly a [] Json array, we need to use *dynamic data = JArray.Parse(sampleJson);*
        dynamic data = JValue.Parse(sampleJson);

        // get fields each by each
        // BUT, by using this way will NOT support by intellisense
        var licenseNo = (string)data.LICSNO;
        var dxid = (string)data.DXIDS.First?.DXID;
    }

img "console writeline"

Unfortunately, System.Text.Json did NOT support this kind of low level reader yet. But we believe that it will do.

Everything Is Unpredictable

One day, in my current work, we have a input shown blow,

    const string unpredictableJsonString = "[{\"NO\": \"1\", \"CNT\": 1, \"AMOUNT\": 100}]";

base on inputs, we create a POCO model to carry data.

    internal class UnpredictableSample
    {
        public string NO { get; set; }
        public int CNT { get; set; }
        public int AMOUNT { get; set; }
    }

However, things went south. We noticed that the parsing function will throw exception sometimes. Tracking data logs, and discussing with client, they told us it's difficult to limit end user's data input.

Errrrr... what was that?

usually, the regular inputs should be "[{"NO": "1", "CNT": 1, "AMOUNT": 100}]";

sometimes it will be "[{"NO": "1", "CNT": 1.0, "AMOUNT": 100}]";

occasionally it might be "[{"NO": "1", "CNT": "1 ", "AMOUNT": 100}]";, yes, as we can see, it's a string with many spacing...

What The F-word ?!

img "chloe-grace-moretz-wtf.gif"

img "chloe-grace-moretz-wtf.gif"

Since our client cannot correct the issue right away, we need to find another way to fix this trouble (shxt) here.

-System.Text.Json

We should change plan, change CNT field into dynamic type to carry data, and add new field named CNT_R (or other name you like) with int type to real-world apply property.

    internal class UnpredictableSample
    {
        public string NO { get; set; }
        public dynamic CNT { get; set; }
        public int CNT_R { get; set; }
        public int AMOUNT { get; set; }
    }

    const string unpredictableSampleJson = "[{\"NO\": \"1\", \"CNT\": 1, \"AMOUNT\": 100}, {\"NO\": \"1\", \"CNT\": 1.0, \"AMOUNT\": 100}, {\"NO\": \"1\", \"CNT\": \"1    \", \"AMOUNT\": 100}]";

Then, we will using JsonElement's ValueKind detecting to determine how CNT value will be converted.

    private static void UnpredictableParsing()
    {
        var result = System.Text.Json.JsonSerializer.Deserialize<IEnumerable<UnpredictableSample>>(unpredictableSampleJson);

        // parsing strategy
        if (result?.Any() == true)
        {
            foreach (var item in result)
            {
                var jElementCNT = (System.Text.Json.JsonElement)item.CNT;
                if (jElementCNT.ValueKind == System.Text.Json.JsonValueKind.Number)
                {
                    // why GetDouble() or GetSingle() ? that's because of 1, or 1.0
                    item.CNT_R = (int)jElementCNT.GetSingle();
                }
                else if (jElementCNT.ValueKind == System.Text.Json.JsonValueKind.String)
                {
                    int.TryParse(jElementCNT.GetString(), out var cnt);
                    item.CNT_R = cnt;
                }
                else
                {
                    item.CNT_R = 0; // default to zero (according to faced situation)
                }
            }
        }
    }

-Newtonsoft.Json

Using a JValue / JArray we mention it earlier to parse data.

    private static void UnpredictableParsing2()
    {
        // IEnumerable data = JValue.Parse(unpredictableSampleJson);
        IEnumerable<dynamic> data = JArray.Parse(unpredictableSampleJson);
        var ret = new List<UnpredictableSample>();
        foreach (var item in data)
        {
            int cnt;
            try
            {
                cnt = Convert.ToInt32(item.CNT);
            }
            catch
            {
                // try NOT parsing value from exception catching, it's a badass
                // it might harm performance
                int.TryParse((string)item.CNT, out cnt);
            }
            ret.Add(new UnpredictableSample {
                NO = item.NO,
                CNT_R = cnt,
                AMOUNT = item.AMOUNT
            });
        }
    }

See, it worked, we got our data parsed successfully.

In my personal opinion, I would like to choose the parsing method named UnpredictableParsing(), because of the intellisense supporting. It will be a great help for our working performance.

Try runnable samples on .Net fiddle

Enjoy it!


References:

https://www.json.org

How to serialize and deserialize (marshal and unmarshal) JSON in .NET

Using JSON.NET for dynamic JSON parsing

小技巧 - JSON 免定義物件 LINQ 操作

使用dynamic簡化Json.NET JObject操作

System.Text.Json!

ISO 8601-1:2019

DateTime and DateTimeOffset support in System.Text.Json

Matt