客製化 NewtonSoft Json Converter

一路用來 NewtonSoft 一直在轉換 Class 到 json 這部份幫上了非常多的忙,一直以來也沒有需要更改的問題,直到最近發現 NewtonSoft 在做 decimal 轉換不如預期,所以打算重新寫一個 json Converter 來解決問題。

若你的Class 裡有使用到了 decimal 的型別,而且是用 newtonsfot json 來將它序列化成 json 字串時

會發現 decimal 即便是整數,但 newton json 還是會幫你帶上 .0 的字串,其實這並沒有什麼大不了

若你的輸出資料裡,decimal 資料若不多,這也不是什麼太大的問題 (因為不會增長太多的資料)

但若是跟你合作的商家不接受這樣的 json 輸出,那問題就大了

有兩種處理的方式

1. 自己組個字串,不要使用 newton json,這是最簡單,最快的方法,但卻不是個好辦法

2. 客製化你自己的 json converter, 這就是這篇的重點

客製化你的 json converter 吧!

首先你必須先繼承 json converter,寫出屬於自己的 json converter


    public class DecimalConverter : JsonConverter
    {
        private void CustomDumpData(JsonWriter writer, object data)
        {

            char doubleQuote = '"';
            StringBuilder result = new StringBuilder();

            foreach (var Property in data.GetType().GetProperties())
            {
                string outputData = "";
                if (Property.PropertyType == typeof(decimal)
                    || Property.PropertyType == typeof(float)
                    || Property.PropertyType == typeof(double)
                    || Property.PropertyType == typeof(int)
                    || Property.PropertyType == typeof(long))
                {
                    //為小數點類型
                    outputData = processNumberData(Property.GetValue(data));
                    result.Append($"{doubleQuote}{Property.Name}{doubleQuote}:{outputData},");
                }
                else
                {
                    outputData = Property.GetValue(data).ToString();
                    result.Append($"{doubleQuote}{Property.Name}{doubleQuote}:{doubleQuote}{outputData}{doubleQuote},");
                }
            }


            string output = result.ToString();
            writer.WriteRawValue(output.Substring(0, output.Length - 1));
        }

        private string processNumberData(object value)
        {
            var data = value.ToString();

            if (data.EndsWith(".0"))
                return data.Substring(0, data.Length - 2);

            if (data.Contains("."))
                return data.TrimEnd('0').TrimEnd('.');

            return data;
        }

        public override void WriteJson(JsonWriter writer, object value,
                                       JsonSerializer serializer)
        {
            writer.WriteRawValue("{");
            CustomDumpData(writer, value);
            writer.WriteRawValue("}");
        }
        
        public override bool CanConvert(Type objectType)
        {
            //只支援 transferModel 使用
            return (objectType == typeof(TransferModel));
        }

        public override bool CanRead
        {
            get { return false; }
        }

        public override object ReadJson(JsonReader reader, Type objectType,
                                        object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

    }

如此一來,我們就寫出了一個屬於自己的 json converter: DecimalConverter

其中CanConvert 是一大重點,目前我們只允許 TransferModel 可以使用我們的客製化 json convert

接著我們把 DecimalConverter 掛到 setting 裡即可

我們所用的 model 如下 

    public class TransferModel
    {
        public string name { set; get; }
        public decimal amount { set; get; }
    }

    public class RegisterModel
    {
        public string name { set; get; }
        public decimal revenue { set; get; }
    }

 

            TransferModel decimalSample = new TransferModel { name = "daship", amount = 2500.01M };

            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.Converters.Add(new DecimalConverter());

            string jsonTransfer = JsonConvert.SerializeObject(decimalSample, settings);

 

但不知你有沒有發現 settings.Converters 可是可以掛一堆 converter 進來的事

若有 A converter, B converter 的話,這兩個 conveters 會怎麼運作呢?

先說結論,還記得我們上面所提到的 CanConvert 這個屬性嗎?

在 newton json 裡,會依序以你加到 setting 裡的 converter 順序去試,如果可以的話,就會挑出來使用

如果不行的話,就會往下找,如果都找不到的話,就會用預設的 json converter 來做轉換

另一個 json converter: CurrencyJsonConverter

我們這邊另外再寫一個 currency converter, 目的是把要貨幣金額小數點拿掉,並在三位數加上一個逗號


    public class CurrencyJsonConverter : JsonConverter
    {
        private void CustomDumpData(JsonWriter writer, object data)
        {

            char doubleQuote = '"';
            StringBuilder result = new StringBuilder();

            foreach (var Property in data.GetType().GetProperties())
            {
                string outputData = "";
                if (Property.PropertyType == typeof(decimal)
                    || Property.PropertyType == typeof(float)
                    || Property.PropertyType == typeof(double)
                    || Property.PropertyType == typeof(int)
                    || Property.PropertyType == typeof(long))
                {
                    //為小數點類型
                    outputData = processNumberData(Property.GetValue(data));
                    result.Append($"{doubleQuote}{Property.Name}{doubleQuote}:{outputData},");
                }
                else
                {
                    outputData = Property.GetValue(data).ToString();
                    result.Append($"{doubleQuote}{Property.Name}{doubleQuote}:{doubleQuote}{outputData}{doubleQuote},");
                }
            }


            string output = result.ToString();
            writer.WriteRawValue(output.Substring(0, output.Length - 1));
        }

        private string processNumberData(object value)
        {
            string data = value.ToString();


            int pointPosition = data.IndexOf(".");
            if (pointPosition < 0)
                return GetCurrencyFormatData(data);

            if (pointPosition == 0)
                return "0";

            //取整數
            string strInteger = data.Substring(0, pointPosition);
            return GetCurrencyFormatData(strInteger);
        }

        private string GetCurrencyFormatData(string input)
        {
            if (input.Length <= 3)
                return input;

            string result = "";
            int digit = 0;
            for (int i = input.Length - 1; i >= 0; i--)
            {
                digit++;
                result = $"{input[i]}{result}";

                if (digit % 3 == 0)
                    result = $",{result}";
            }

            if (result.StartsWith(","))
                return result.Substring(1);

            return result;
        }

        public override void WriteJson(JsonWriter writer, object value,
                                       JsonSerializer serializer)
        {
            writer.WriteRawValue("{");
            CustomDumpData(writer, value);
            writer.WriteRawValue("}");
        }
        
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(RegisterModel));
        }

        public override bool CanRead
        {
            get { return false; }
        }

        public override object ReadJson(JsonReader reader, Type objectType,
                                        object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

而這邊的 CanConvert 則是只接受 RegisterModel

 

            TransferModel decimalSample = new TransferModel { name = "acelee", amount = 2500.00M };
            RegisterModel registerSample = new RegisterModel { name = "acelee", revenue = 2500.00M };
            var normalSample = new  { name = "acelee", amount = 2500.00M };

            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.Converters.Add(new DecimalConverter());
            settings.Converters.Add(new CurrencyJsonConverter());


            string decimalJson = JsonConvert.SerializeObject(decimalSample, settings);
            string registerJson = JsonConvert.SerializeObject(registerSample, settings);
            string normalJson = JsonConvert.SerializeObject(normalSample, settings);

輸出分別是
{"name":"acelee","amount":2500}  (DecimalConverter match,移除小數點部份)
{"name":"acelee","revenue":2,500} (CurrencyJsonConverter match, 移除小數點部份,另外加上千分位上加逗號)
{"name":"acelee","amount":2500.00} (DecimalConverter, CurrencyJsonConverter 都不符合,所以就用原本的 json converter)

參考資料

http://blog.darkthread.net/post-2014-06-13-trim-json-trail-zero.aspx