---
# System prepended metadata

title: Json.Net 偷埋的地雷們
tags: [c#, UWP, json]

---

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