XML 을 JSON 으로 변환하는 것은 기계적으로 들리지만, JSON 에 직접적인 등가물이 없는 XML 부분들에 곧 부딪히게 됩니다: 속성, 텍스트와 요소가 섞인 콘텐츠, 한 번이나 여러 번 나타나는 요소, 그리고 네임스페이스입니다. 하나의 "올바른" 매핑은 없고 관례만 있습니다. 본 가이드는 표준 관례, 내려야 할 결정, 그리고 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 모양은 스키마가 아니라 데이터에 의존합니다:
<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 네임스페이스는 xmlns 선언에 묶인 접두사(soap:Envelope)를 씁니다. 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 to XML: 루트 요소, 배열, 속성 매핑 —— 반대 방향
- CSV 와 XML 을 JSON 으로 변환 —— CSV 포함
- JSON 이란? —— 대상 포맷의 타입과 규칙