# Json.Net 偷埋的地雷們 ###### tags: `UWP`, `c#`, `json` > 還是萬分感謝 victor 大大幫忙把地雷挖出來(汗 [color=#26daff] ## 問題描述 打 `TreatmentProcedure` 的API時,帶上去的只有一個 Tooth 牙位= FM,回來時卻有 ==兩個==Tooth牙位都是FM,而且==後端其實是正確的==喔! (i.e. 只有一個 Tooth 牙位= FM) ```csharp // ApiService.cs public async Task<TreatmentProcedure> CreateTreatmentProcedure(object tp) { try { // tp.Teeth.Count = 1 var newTP = await _adapter.CreateTreatmentProcedure(tp); return newTP; // newTp.Teeth.Count = 2 (why??????) } catch (Exception e) { HandleEx(e); } return null; } ``` ```csharp // TreatmentProcedure.cs public class TreatmentProcedure : Observable { private List<Tooth> _teeth; [JsonProperty(PropertyName = "teeth")] public List<Tooth> Teeth { get => _teeth; set => Set(ref _teeth, value); } private NhiProcedure _nhiProcedure; [JsonProperty(PropertyName = "nhiProcedure")] public NhiProcedure NhiProcedure { get => _nhiProcedure; set { Set(ref _nhiProcedure, value); var restrictedTeethPosition = TeethRuleHelper.GetRestrictedToothPosition(value); if (!string.IsNullOrEmpty(restrictedTeethPosition)) { Teeth = new List<Tooth>() { new Tooth() { Position = restrictedTeethPosition // "FM" } }; } } } } ``` ### 原因 > [StackOverflow: Json.net deserializing list gives duplicate items](https://stackoverflow.com/questions/13394401/json-net-deserializing-list-gives-duplicate-items) ==Deserialzing list== 的邏輯是: * if the list is `null`: create a new list and assign via the property setter * if the list exists: deserialize each item in turn, and `append` to the list > 可見我在 Deserialze + assign `TreatmentProcesure.NhiProcedure` 時,就已經 create `Teeth` 了! 所以在 Deserialze `TreatmentProcesure.Teeth` 就變成 Append 而非 assign 了 [color=skyblue] ### 解法 > [StackOverflow: Replace when deserialzing](https://stackoverflow.com/questions/33744236/how-to-apply-objectcreationhandling-replace-to-selected-properties-when-deserial) ##### 方法1. use `array` instead of `List` ==Deserialzing Array== 的邏輯是: * deserialize each item in turn, and append to a temporary list * convert the list to an array , and assign via the setter 所以最後的 assign 會直接不管原值直接蓋掉~ ##### 方法2. Add `ObjectCreationHandling ` ```csharp private List<Tooth> _teeth; [JsonProperty(PropertyName = "teeth", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List<Tooth> Teeth { get => _teeth; set => Set(ref _teeth, value); } ``` ###### 補充~ | ObjectCreationHandling | Description | | -------- | -------- | | Auto(default) | 若原值存在則add, 不然就create | | Reuse | 僅原值存在時進行add | | Replace | 一律 Create |   >[Explanation for ObjectCreationHandling](https://stackoverflow.com/questions/27848547/explanation-for-objectcreationhandling-using-newtonsoft-json) ##### 方法3. 嗯...還是少在setter裡做太多事(特別是 ==list== 的操作:bangbang:)hen 危險的 > < ## 其他要小心的部分 ##### [`DefaultValueHandling`](https://www.techrepository.in/json-net-handling-default-values) | DefaultValueHandling | Description | | -------- | -------- | | Include | deserializing 遇到預設値會照單全收 | | Ignore | deserializing 遇到預設値會==忽略==(e.g. nullable types: `null` , integers/decimals/float: `0`, bool: `false`) | | Populate | 若json裡沒有出現此property, 但我們有另外用`[DefaultValue()]` 設値的話,deserializing會有我們設的値 | | IgnoreAndPopulate | ignore+Populate | ##### [`MemberSerialization`](https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_MemberSerialization.htm) | MemberSerialization | Description | | -------- | -------- | | OptOut | All `public` members are serialized (除了` JsonIgnore`)| | OptIn | Only members marked with `JsonPropertyAttribute` or `DataMemberAttribute` are serialized. | | Fields |All public and private fields are serialized. |