文章目錄
decimal 屬性輸出 JSON 時指定的格式問題
這是之前專案遇到的狀況:輸出 金額
時只需處理到小數點下二位。既然是 金額
,為了避免精準度造成的誤差都會選用 deciaml
資料類型,而在 db 中使用 money
儲存(因為業務需求面沒有運算需求可以使用,如果會有運算 money
有失真的風險,詳細內容請參考 欄位開立(2) - decimal, numeric, float, real, money 的抉擇),預設精準度為小數點下四位,為了符合目前系統的要求(小數點下二位),就需要調整輸出,來看看可以怎麼做吧
前提設定
自訂 model 中的 decimal 屬性
public class TestData { public decimal Salary { get; set; } }
可能存在不同小數點位數的值
var test = new TestData() { Salary = 1.03355M }; var test2 = new TestData() { Salary = 2.0000M }; var test3 = new TestData() { Salary = 3M }; var test4 = new TestData() { Salary = 4.115M };
原始輸出
#region - data1 - var test = new TestData() { Salary = 1.03355M }; test.Dump(); JsonConvert.SerializeObject(test).Dump(); #endregion #region - data2 - var test2 = new TestData() { Salary = 2.0000M }; test2.Dump(); JsonConvert.SerializeObject(test2).Dump(); #endregion #region - data3 - var test3 = new TestData() { Salary = 3M }; test3.Dump(); JsonConvert.SerializeObject(test3).Dump(); #endregion #region - data4 - var test4 = new TestData() { Salary = 4.115M }; test4.Dump(); JsonConvert.SerializeObject(test4).Dump(); #endregion
可以看到除了整數(3)被加上一個小數位(3.0)之外,其他數值都會完整輸出
使用私有欄位儲存,在 get 時格式化
加上私有欄位並格式化輸出
public class TestData { private decimal _salary; public decimal Salary { get { return Math.Round(_salary, 2); } set { _salary = value; } } }
結果
缺點
可以看到整數儲存與輸出 json 不符合,這個是 json.net 的特性,另外不一定可以完全符合指定小數位數
改善
如果有強烈的需求還是可以強制調整特定輸出,但這樣會有執行效率不佳的問題要特別留意
public class TestData { private decimal _salary; public decimal Salary { get { return decimal.Parse(Math.Round((double)_salary, 2).ToString("0.00")); } set { _salary = value; } } }
客製 Json.Net 的 JsonConverter
加入自訂 JsonConverter 並繼承 JsonConverter
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; writer.WriteValue(Math.Round(_value, _precision, _rounding)); } }
在欲指定精準度的屬性上加入 attribute
public class TestData { [JsonConverter(typeof(RoundingJsonConverter),2)] public decimal Salary { get; set; } }
結果
缺點
一樣有指定小數位數未生效的狀況,以下指定小數四位為例
改善
一樣會有效能問題
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')))); } }
心得
事實上專案中的介接目標系統沒有強制要求所有有數字都需精準至小數下二位,只是調整過程中潔癖發作,一直想要調整到人眼看也是很整齊,不過實在很難,最後還是選了個不漂亮的做法,當然主因就是找不到更好的方式XD,學藝不精,只好先紀錄一下日後有能力或是緣份到了再來改寫
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-03
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。