文章目錄
decimal , double , float 輸出 json 的格式問題
之前筆記 decimal 屬性輸出 JSON 時指定的格式問題 提到在專案中因為系統介接需要統一 decimal 小數位數,過程中也才發現 json.net 在輸出沒有小數的 decimal 時行為不太一樣(會補上 .0
:小數點及小數點一位),最後雖然有解決問題,但解決方式自己卻不甚滿意,加上想要順帶測試 double 及 float 的行為,所以又花了一些時間找其他方法,就來看看過程遇到的問題及最後的解決方式吧
前提設定
自訂 model (包含 decimal , double , float 屬性)
public class TestData { public decimal Salary { get; set; } public double ExRate { get; set; } public float TaxRate { get; set; } }
使用 jsonconvert
public class RoundingJsonConverter : JsonConverter { //指定精準度 int _precision; //指定四捨五入的傾向 MidpointRounding _rounding; //預設精準度小數點下 4 位 public RoundingJsonConverter() : this(4) { } public RoundingJsonConverter(int precision) : this(precision, MidpointRounding.AwayFromZero) { } public RoundingJsonConverter(int precision, MidpointRounding rounding) { _precision = precision; _rounding = rounding; } public override bool CanRead { get { return false; } } public override bool CanConvert(Type objectType) { return objectType == typeof(decimal); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { decimal _value = (decimal)value; //為值補 0 writer.WriteValue(decimal.Parse(Math.Round(_value, _precision, _rounding).ToString("0.".PadRight(2 + _precision, '0')))); } }
遇到問題及解決方式
Math.Round 處理 float 會造成數值不正確
Math.Round 沒有 float 版本,使用 Math.Round 會隱含將 float 轉型為 double,而造成數值不正確
原始作法
var value= 0.225f; Math.Round(value,2,MidpointRounding.AwayFromZero).Dump();
數值不正確
Math.Round 傳入 float 會隱含轉型 double
新做法:先轉型為
decimal
Math.Round(Convert.ToDecimal(value),2,MidpointRounding.AwayFromZero).Dump();
為了指定輸出格式頻繁轉型 (ToString 再 parse 回 decimal)
WriteJson 時移除 decimal.parse 與 Math.Round 並使用
WriteRawValue
方法原始做法
writer.WriteValue(Math.Round(_value, _precision, _rounding).ToString("0.".PadRight(2 + _precision, '0')));
新做法
writer.WriteRawValue((Convert.ToDecimal(value)).ToString("0.".PadRight(2 + _precision, '0')));
完整 jsonconvert 程式碼
public class RoundingJsonConverter : JsonConverter { //指定精準度 int _precision; //指定四捨五入的傾向 MidpointRounding _rounding; //預設精準度小數點下 4 位 public RoundingJsonConverter() : this(4) { } public RoundingJsonConverter(int precision) : this(precision, MidpointRounding.AwayFromZero) { } public RoundingJsonConverter(int precision, MidpointRounding rounding) { _precision = precision; _rounding = rounding; } public override bool CanRead { get { return false; } } public override bool CanConvert(Type objectType) { return objectType == typeof(decimal); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { decimal _value = (decimal)value; //為值補 0 writer.WriteRawValue((Convert.ToDecimal(value)).ToString("0.".PadRight(2 + _precision, '0'))); } }
整數不輸出小數點及小數位
加上屬性來控制是否將數字小數位末端的
0
移除原做法
新做法
加入屬性用來判斷是否要移除
//用來判斷是否移除最後的 0 bool _truncate; public RoundingJsonConverter(int precision, bool truncate) : this(precision, MidpointRounding.AwayFromZero, truncate) { } public RoundingJsonConverter(int precision) : this(precision, MidpointRounding.AwayFromZero, false) { } public RoundingJsonConverter(int precision, MidpointRounding rounding, bool truncate) { _precision = precision; _rounding = rounding; _truncate = truncate; }
加入
0
的處理流程if (_truncate) { var _result = Math.Round(_value, _precision, _rounding) / 1.000000000000000000000000000000000m;//移除0 if (Int64.TryParse(_result.ToString(), out var longresult))//處理整數 writer.WriteValue(longresult); else//處理非整數 writer.WriteValue(_result); }
在 model 上加入是否移除
0
public class TestData { [JsonConverter(typeof(RoundingJsonConverter), 2, true)] public decimal Salary { get; set; } [JsonConverter(typeof(RoundingJsonConverter), 2, true)] public double ExRate { get; set; } [JsonConverter(typeof(RoundingJsonConverter), 2, true)] public float TaxRate { get; set; } }
完整程式碼
void Main() { var test = new TestData() { Salary = 1.995m, ExRate = 2.295D, TaxRate = 1f }; test.Dump(); JsonConvert.SerializeObject(test).Dump(); } public class TestData { [JsonConverter(typeof(RoundingJsonConverter), 2, true)] public decimal Salary { get; set; } [JsonConverter(typeof(RoundingJsonConverter), 2, true)] public double ExRate { get; set; } [JsonConverter(typeof(RoundingJsonConverter), 2, true)] public float TaxRate { get; set; } } public class RoundingJsonConverter : JsonConverter { //指定精準度 int _precision; //指定四捨五入的傾向 MidpointRounding _rounding; //用來判斷是否移除最後的 0 bool _truncate; //預設精準度小數點下 4 位 public RoundingJsonConverter() : this(4) { } public RoundingJsonConverter(int precision, bool truncate) : this(precision, MidpointRounding.AwayFromZero, truncate) { } public RoundingJsonConverter(int precision) : this(precision, MidpointRounding.AwayFromZero, false) { } public RoundingJsonConverter(int precision, MidpointRounding rounding, bool truncate) { _precision = precision; _rounding = rounding; _truncate = truncate; } public override bool CanRead { get { return false; } } public override bool CanConvert(Type objectType) { return objectType == typeof(decimal) | objectType == typeof(double) | objectType == typeof(float); //return objectType == typeof(decimal); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var _value = Convert.ToDecimal(value); if (_truncate) { var _result = Math.Round(_value, _precision, _rounding) / 1.000000000000000000000000000000000m;//移除0 if (Int64.TryParse(_result.ToString(), out var longresult))//處理整數 writer.WriteValue(longresult); else//處理非整數 writer.WriteValue(_result); } else writer.WriteRawValue(_value.ToString("0.".PadRight(2 + _precision, '0'))); } }
心得
一開始從想要統一 decimal 的輸出格式,到後來持續調整寫法,到額外擴充支援 double 與 float,最終也可以自訂是否輸出可以刪除的小數點及 0,也許再次用到的機會並不高,但為了 coding for fun 帶來的樂趣無價呀
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-03
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。