# 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. |