昨天理解 Request Body 之後,我開始知道,POST 傳來的資料可能會放在 Body 裡。
例如:
{"name":"Andy","age":20}
但這時又出現一個很直覺的問題。
這明明只是一串文字。
為什麼平常在 ASP.NET Core 裡,卻可以直接拿到:
Name = Andy
Age = 20
甚至直接得到一個 C# 物件?
所以今天想理解的問題就是:
JSON 明明只是一串文字,Framework 為什麼可以把它變成 C# 物件?
💭 我原本以為……
以前寫 Web API 時,我很容易把:
Client 傳 JSON
↓
Controller 收到 Model
當成一件很自然的事情。
例如 Client 傳:
{"name":"Andy","age":20}
Server 端可能就可以直接使用:
user.Name
user.Age
以前會覺得,好像 JSON 本身就知道 C# Class 長什麼樣。
但其實這當然不可能。
Client 傳來的只是文字資料。
而 C# 裡的物件則是記憶體中的程式資料結構。
中間一定還需要一個轉換步驟。
JSON 到底是什麼?
JSON 是一種文字格式。
例如:
{"name":"Andy","age":20}
它本身只是由一些字元組成。
其中:
"name"
是一個欄位名稱。
"Andy"
是一個字串值。
"age"
也是欄位名稱。
20
是一個數值。
所以 JSON 的目的,是用一種固定格式描述資料。
可以把它理解成:
文字
↓
但有結構
它不是單純的一句話,而是一種規則明確的資料表示方式。
為什麼 Framework 看得懂?
因為 JSON 有自己的格式規則。
例如:
{}
代表 Object。
[]
代表 Array。
:
用來分隔欄位名稱和值。
,
用來分隔不同欄位。
所以:
{"name":"Andy","age":20}
Framework 並不是「看字猜意思」。
而是按照 JSON 規則解析。
可以想成:
Raw JSON
↓
找到 Object
↓
找到 name
↓
找到 "Andy"
↓
找到 age
↓
找到 20
最後整理成:
name → Andy
age → 20
這個過程就是 Parsing。
從 JSON 變成 C# 物件又多了一步
光把 JSON 解析成:
name → Andy
age → 20
還不代表它已經是 C# 物件。
假設程式裡有一個:
User
它有:
Name
Age
那接下來還要做:
JSON 欄位 name
↓
對應 User.Name
JSON 欄位 age
↓
對應 User.Age
最後才建立:
User
├── Name = Andy
└── Age = 20
這個從資料格式轉成程式物件的過程,通常稱為:
Deserialization
也就是「反序列化」。
Serialization 和 Deserialization 是什麼?
這兩個名詞很常一起出現。
Serialization 可以理解成:
程式物件
↓
轉成某種可儲存或傳輸的格式
例如:
C# User Object
↓
JSON
↓
{"name":"Andy","age":20}
而 Deserialization 則反過來:
JSON
↓
轉回程式物件
也就是:
{"name":"Andy","age":20}
↓
User
├── Name = Andy
└── Age = 20
所以:
Serialization
→ Object 變成資料格式
Deserialization
→ 資料格式變成 Object
HTTP Body 和 JSON 是同一件事嗎?
不是。
這裡很容易混在一起。
Request Body 是 HTTP Message 裡的一部分。
JSON 則是一種資料格式。
所以:
Request Body
↓
裡面可能放 JSON
但 Body 不一定是 JSON。
也可能是:
純文字
HTML
Form Data
Binary Data
其他格式
所以 Framework 要先看:
Content-Type
如果看到:
Content-Type: application/json
才知道:
這個 Body 應該按照 JSON 來理解。
整個流程就變成:
Request
↓
Headers
↓
Content-Type = application/json
↓
Body
↓
JSON
↓
Deserialization
↓
C# Object
Content-Type 再次變重要
前幾天學到:
Content-Type
用來描述 Body 的資料類型。
今天它開始直接決定:
Framework 要用什麼方式解析 Body。
例如:
Content-Type: application/json
Framework 會把 Body 當成 JSON。
如果是:
Content-Type: text/plain
那 Body 可能只當成一般文字。
所以 Framework 不會只看到 Body 就立刻反序列化。
它通常會先理解:
這份 Body 是什麼格式?
再決定:
應該用哪一種 Parser。
System.Text.Json 是做什麼的?
在 .NET 裡,一個常用的 JSON 處理工具就是:
System.Text.Json
它可以把 C# 物件轉成 JSON。
也可以把 JSON 反序列化成 C# 物件。
例如概念上:
JSON
↓
JsonSerializer.Deserialize()
↓
User Object
所以 Framework 並不是自己一個字一個字猜:
這是 Name。
這是 Age。
而是會使用專門的 JSON Parser / Serializer 來理解 JSON 格式。
Framework 只是幫我們把這些步驟串起來。
那 ASP.NET Core 為什麼可以直接給我 Model?
假設 Client 傳:
POST /users
Body:
{"name":"Andy","age":20}
ASP.NET Core 可能會做很多事情。
概念上可以先理解成:
收到 HTTP Request
↓
解析 Headers
↓
知道 Content-Type 是 application/json
↓
讀取 Request Body
↓
使用 JSON Serializer
↓
建立指定的 C# Model
↓
把 Model 交給 Handler / Controller
所以我們平常看到的:
User user
其實已經是 Framework 做完很多工作的結果。
Model Binding 又是什麼?
這裡開始會碰到一個常見名詞:
Model Binding
簡單理解,Framework 會嘗試把 Request 裡的資料,整理成 Handler 所需要的參數或 Model。
資料來源可能包括:
Route
Query
Headers
Form
Body
例如:
/users/10
可能把 10 綁定成:
id = 10
而:
?name=Andy
可能綁定成:
name = Andy
JSON Body:
{"name":"Andy","age":20}
則可能轉成:
User user
所以 Model Binding 可以理解成:
Request 裡的資料
↓
Framework 整理
↓
對應到程式參數 / Model
而 JSON Deserialization 則可能是這個過程中的其中一部分。
為什麼這件事情對 Mini Web Framework 很重要?
因為如果我們之後希望自己的 Framework 使用方式變得像:
Handler(User user)
那 Framework 就必須知道:
Request Body 在哪裡?
Content-Type 是什麼?
Body 是不是 JSON?
JSON 怎麼解析?
JSON 要轉成哪一個 C# Type?
所以真正做到這裡時,Framework 已經不只是:
接 Request
↓
找 Route
↓
回 Response
而是開始幫 Handler 準備資料。
也就是:
HTTP
↓
Framework
↓
C# 世界
這個轉換開始變得更完整。
今天的認知更新
以前的我:
JSON
↓
C# Object
現在的我:
Client
↓
HTTP Request
↓
Headers
↓
Content-Type
↓
Body
↓
JSON Text
↓
JSON Parsing
↓
Deserialization
↓
C# Object
↓
Handler 使用
也就是說:
JSON 並不會自己變成 C# Object。
Framework 必須先知道 Body 的資料格式,再使用相對應的 Parser 和 Serializer,最後才建立程式物件。
今日最大的發現
今天最大的發現是:
HTTP 和 JSON 其實是兩個不同層次。
HTTP 負責:
Request 怎麼傳
Headers 怎麼表示
Body 在哪裡
JSON 負責:
Body 裡面的資料要怎麼表示
最後 Framework 再負責:
把 HTTP 裡的 JSON
↓
轉成 C# 可以使用的 Object
所以可以整理成三層:
TCP
↓
傳 bytes
HTTP
↓
定義 Request / Response 格式
JSON
↓
定義資料格式
Framework
↓
把這些底層資料轉成 C# Object
這讓前幾天學過的東西又串起來了。
在 .NET 中,可以使用 System.Text.Json 進行 JSON Serialization 與 Deserialization。
也就是可以把物件轉成 JSON,也可以把 JSON 轉成指定的 .NET Type。
ASP.NET Core 則會在 Request Processing 過程中處理 Request Body,並根據設定與 Content-Type 等資訊,將資料轉換成應用程式可以使用的形式。
所以平常在 Handler 或 Controller 裡直接取得 Model,其實是一層很高階的抽象。
背後仍然需要:
讀 Body
↓
判斷 Content-Type
↓
解析 JSON
↓
建立 Object
🛠 與 Mini Web Framework 的關聯
目前 Mini Web Framework 的流程已經慢慢變成:
Browser
↓
TCP
↓
HTTP Request
↓
HttpRequest
├── Method
├── Path
├── Headers
├── Query
└── Body
↓
Routing
↓
Handler
而今天又多出一層:
Body
↓
Content-Type
↓
JSON
↓
Deserialization
↓
C# Object
未來如果繼續做下去,就有機會讓 Handler 不再直接接觸:
raw JSON string
而是直接使用:
C# Model
這會讓 Framework 又更靠近平常使用 ASP.NET Core 的感覺。
今天暫時不實作
今天一樣先把概念想清楚。
因為 Day16~Day19 的 Request Parsing 實作之後還需要一起補完。
等那些基礎補上後,再實作 JSON Deserialization 會比較合理。
否則如果連 Request Body 都還沒有真正讀完整,就直接做 JSON Deserialize,會把中間很重要的 HTTP 問題跳過。
所以目前先記住整個方向:
Raw Body
↓
Content-Type
↓
JSON Parser
↓
Deserialization
↓
C# Object
之後再把它真正加入 Mini Web Framework。
結尾
今天原本只是想知道:
為什麼 JSON 可以變成 C# Object?
但一路拆下來後,我才發現這件事情其實跨越了好幾層。
Client 最開始只是把資料放進 HTTP Request Body。
Framework 必須先讀到完整 Body,再透過 Content-Type 知道它是 JSON。
接著才能讓 JSON Serializer 去解析文字,最後建立 C# Object。
所以以前看到:
User user
會覺得這只是一個很普通的 Handler Parameter。
但現在可以看到它背後其實經過:
TCP bytes
↓
HTTP Request
↓
Request Body
↓
JSON
↓
Deserialization
↓
User Object
這也是 Framework 一直在替開發者做的事情:
把底層格式和傳輸細節,逐步轉換成我們熟悉的程式概念。
現在 Framework 已經開始可以想像把:
JSON
↓
轉成 C# Object
但這時又有一個新的問題。
如果 Handler 想要的不是同一種資料呢?
例如:
/users 需要 User
/products 需要 Product
/login 需要 LoginRequest
Framework 怎麼知道每個 Handler 到底需要什麼參數?
難道每個 Route 都要自己寫一套解析邏輯嗎?
所以新的問題會是:
Framework 到底怎麼知道 Handler 需要哪些參數?