把 XML 轉換為 JSON 聽起來很機械,但你很快就會撞上 XML 中 JSON 沒有直接對等物的部分:屬性、文字與元素混合的內容、出現一次或多次的元素、以及命名空間。這裡沒有唯一「正確」的對映 —— 只有慣例。本指南講解標準慣例、你必須做的決定,以及如何在 JavaScript、Python 與瀏覽器中把 XML 轉為 JSON。
對映一覽
多數 XML 轉 JSON 工具(xmltodict、fast-xml-parser,本站工具也一樣)都遵循同一種形態:每個元素變成一個物件,屬性變成帶特殊前綴的鍵,文字要麼成為值、要麼放到一個保留鍵裡。
<!-- XML -->
<note id="1" priority="high">
<to>Ada</to>
<from>Bob</from>
<body>Hello & welcome</body>
</note>// JSON
{
"note": {
"@id": "1",
"@priority": "high",
"to": "Ada",
"from": "Bob",
"body": "Hello & welcome"
}
}屬性 → @ 前綴鍵
JSON 物件沒有屬性的概念,因此一個近乎通用的慣例是給屬性名稱加上 @ 前綴。這樣它們與子元素能區分開來,對映也可以反向。
<book id="b1" lang="en"/>
→ { "book": { "@id": "b1", "@lang": "en" } } 有些工具用別的前綴($、_),或用一個巢狀的 "@attributes" 物件。挑一種就一以貫之 —— 下游程式需要知道屬性在哪。
文字節點與混合內容
當一個元素只包含文字時,它會塌縮為一個字串值。但當元素同時擁有屬性與文字時,文字需要一個去處 —— 約定俗成的位置是鍵 #text。
<price currency="USD">9.99</price>
→ { "price": { "@currency": "USD", "#text": "9.99" } }
<title>Effective TypeScript</title>
→ { "title": "Effective TypeScript" }真正的混合內容(文字與子元素交錯出現,像 HTML 一類的標記)是最棘手的情況 —— 多數資料導向的轉換器會拼接或丟棄零散的文字。如果你的 XML 是文件風格而非資料風格,預計這裡會有損。
單一 vs 陣列 的問題
這是比其他都更容易把程式碼害死的雷。一個出現一次的元素會變成物件;同一個元素出現兩次就變成陣列。所以 JSON 的形態取決於資料,而不是 schema:
<tags><tag>a</tag></tags>
→ { "tags": { "tag": "a" } } // 物件
<tags><tag>a</tag><tag>b</tag></tags>
→ { "tags": { "tag": ["a", "b"] } } // 陣列 期望 tags.tag 始終是陣列的下游會在單項情況下崩潰。兩個解法:把解析器設定成對已知可重複的元素總是當成陣列處理,或者在解析之後做正規化(const arr = [].concat(node.tag ?? []))。
命名空間
XML 命名空間使用前綴(soap:Envelope)並透過 xmlns 宣告繫結。JSON 沒有命名空間概念,所以轉換器通常採用以下做法之一:
- 把前綴保留在鍵裡 ——
"soap:Envelope"。簡單且可反向,但鍵裡含冒號,需要以方括號存取(obj["soap:Envelope"])。 - 丟掉前綴 ——
"Envelope"。鍵更乾淨,但你會失去命名空間,而且兩套使用同一個本地名的命名空間之間可能會撞鍵。 - 把
xmlns當屬性保留 —— 宣告變成"@xmlns:soap"之類的鍵,使繫結能在往返中存活。
對多數資料任務而言,把前綴保留在鍵裡是最安全的預設 —— 它永遠不會遺失資訊。
實體與 CDATA
正確的轉換器會把五個預定義實體(<、>、&、"、')以及數字引用(©)解碼為對應字元,並把 <![CDATA[...]]> 區塊視為字面文字。
慣例的名字:BadgerFish、GData、Parker
「屬性用 @、文字用 #text」並不是江湖上唯一的方案。讀其他系統的 XML→JSON 輸出時,會遇到這三個有名字的慣例:
- BadgerFish —— 屬性放在以
@為前綴的鍵之下;文字放到$;命名空間宣告放到@xmlns。囉嗦但無損。 - GData —— Google 的變體:屬性帶
$前綴;文字放在$t之下;重複元素總是變成陣列。無損且形態可預期。 - Parker —— 完全捨棄屬性;最簡單也最有損的對映。在你同時掌控兩端、只關心元素值時有 用。
在與一個已經把 XML 轉成 JSON 的系統整合時,請先辨識它使用哪一種慣例,再寫解析程式碼。
用 JSONPath 查詢轉換結果
一旦 XML 被轉好,就可以用 JSONPath 來尋址值。相較於 XPath 的習慣,兩處小調整:
- 屬性鍵帶著對映中的
@前綴,所以 XPath 的@id在 JSONPath 裡是$..['@id']。 - 上面提到的「單一 vs 陣列」意味著像
book/title這樣一條本來能用的 XPath,在 JSONPath 裡可能需要寫成$..book[*].title才能相容兩種形態。
用程式碼把 XML 轉為 JSON
// JavaScript(瀏覽器) —— DOMParser + 一個小走樹器,或者一個函式庫:
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@' });
const obj = parser.parse(xmlString);
# Python —— xmltodict 會把屬性對映到 "@name",文字對映到 "#text"
import xmltodict, json
doc = xmltodict.parse(xml_string)
print(json.dumps(doc, indent=2))線上把 XML 轉為 JSON
想快速轉換,把 XML 貼進 JSON ⇄ XML 轉換器 並點擊 To JSON。它套用上述慣例 —— 屬性用 @、混合內容用 #text、重複元素用陣列 —— 並完全在你的瀏覽器中執行,因此內部資料流與 API 負載永遠不會離開你的機器。
常見問題
XML 屬性在 JSON 中如何表示?
依慣例,它們變成以 @ 為前綴的鍵(例如 @id),與子元素區分開來,使對映可以反向。
為什麼同一個元素有時變成物件、有時變成陣列?
因為形態跟隨資料:一次出現對映為 物件,多次出現對映為陣列。請把解析器設定成對已知可重複的元素一律按陣列處理,或者在解析之後用 [].concat(value) 正規化。
同時擁有屬性的元素的文字去了哪裡?
放到保留鍵 #text 之下,因為物件已經容納了屬性。只有文字的元素則塌縮為一般字串。
XML 命名空間怎麼處理?
JSON 沒有命名空間。最安全的做法是把前綴保留在鍵裡("soap:Envelope"),同時把 xmlns 宣告作為 @xmlns:* 屬性保留,這樣什麼都不會遺失。
相關工具與指南
- JSON ⇄ XML 轉換器 —— 在瀏覽器中雙向轉換
- JSON 轉 XML:根元素、陣列與屬性對映 —— 反方向
- 如何把 CSV 與 XML 轉為 JSON —— 包含 CSV
- 什麼是 JSON? —— 目標格式的型別與規則