이 변환기는 XML 문서에 대한 순수 Ruby 클래스를 씁니다. 모든 XML 변환기가 공유하는 규칙으로 먼저 XML을 데이터로 매핑하고, 클래스는 그 데이터를 JSON.parse했을 때의 해시를 감쌉니다. 속성이나 자식이 있는 요소마다 attr_reader, 키워드 인수 initialize, from_hash, from_json, to_h, to_json이 있는 클래스가 생깁니다.
Ruby는 동적 타입 언어라 출력에 타입 표기가 없고, 숫자·불리언 해석 옵션은 데이터의 모양만 바꿀 뿐 코드는 바꾸지 않습니다. 해시 키는 데이터 키 그대로여서 속성 sku는 data['@sku']에서 읽어 sku로 노출됩니다. 이 클래스는 해시가 아니라 요소를 돌려주는 Nokogiri나 REXML용이 아닙니다.
입력은 엄격한 XML 1.0 파서가 읽고 오류의 행과 열을 보여 주며 20 MiB까지 받습니다. 브라우저를 벗어나는 데이터는 없습니다.
사용 방법
XML 붙여넣기
문서를 붙여 넣거나 예제를 엽니다. 입력하는 동안 Ruby 클래스가 다시 만들어집니다.
매핑 설정
XML 데이터 매핑 패널에서 속성 접두사와 텍스트 키를 고르고 항상 배열 여부를 정합니다. 숫자 추론은 Ruby 코드를 바꾸지 않습니다.
루트 이름 정하기
루트 클래스 이름(기본
Payload)을 입력합니다. 중첩 클래스는 부모 이름을 접두사로 씁니다.클래스 읽기
각 클래스는 리더를 나열한 뒤 키워드 초기화 메서드를 갖고, 선택 키는
nil기본값으로 맨 뒤에 옵니다.복사 또는 다운로드
코드를 복사하거나 루트 이름의 스네이크 케이스인
payload.rb로 내려받습니다.
작동 원리
생성되는 메서드가 하는 일
클래스마다 메서드 네 개가 만들어집니다. 매핑된 해시와 객체 사이를 변환하며, 파일 맨 위의 require "json"을 빼면 Ruby 표준 라이브러리만 씁니다.
| 메서드 | 동작 |
|---|---|
initialize(id:, customer:, item:) | 키워드 인수이며 선택 키는 기본값 nil |
self.from_hash(data) | data['@id'] 같은 문자열 키를 읽고 중첩 객체를 만들며 리스트는 map으로 변환 |
self.from_json(json) | from_hash(JSON.parse(json)) |
to_h | '@id', '#text'를 포함해 같은 문자열 키로 다시 씀 |
to_json(*args) | to_h.to_json(*args)라서 JSON.generate가 동작 |
이름, 선택 키, 리스트
리더 이름은 접두 문자를 뺀 키의 스네이크 케이스라서 @data-id는 data_id, xml:lang은 xml_lang입니다. class 같은 Ruby 키워드에는 접미사가 붙어 class_value가 됩니다. 해시 키에는 항상 실제 철자가 남습니다.
반복 요소의 모든 발생은 키의 합집합을 가진 클래스 하나가 됩니다. 일부에서 빠진 키는 initialize에서 nil 기본값을 갖고 from_hash는 그 키를 그냥 nil로 읽습니다. 반복 요소는 data['item'].map { |v| ... }로 읽습니다.
def self.from_hash(data)
new(
sku: data['@sku'],
qty: data['@qty'],
text: data['#text']
)
end자식 하나일 때와 여럿일 때
<item>이 하나면 중첩 해시에 from_hash를, 여럿이면 map을 씁니다. 해시에 map을 호출하면 from_hash가 읽을 수 없는 키·값 쌍이 나오므로, 항목 개수가 달라질 수 있으면 자식 요소는 항상 배열로 옵션을 켜세요.
실제 예시
<order id="1042"><item sku="A1" qty="1">Pen</item><item sku="B7">Ink</item></order>require "json"
class Payload
attr_reader :order
def initialize(order:)
@order = order
end
def self.from_hash(data)
new(
order: PayloadOrder.from_hash(data['order'])
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'order' => @order.to_h
}
end
def to_json(*args)
to_h.to_json(*args)
end
end
class PayloadOrder
attr_reader :id, :item
def initialize(id:, item:)
@id = id
@item = item
end
def self.from_hash(data)
new(
id: data['@id'],
item: data['item'].map { |v| PayloadOrderItem.from_hash(v) }
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'@id' => @id,
'item' => @item.map { |v| v.to_h }
}
end
def to_json(*args)
to_h.to_json(*args)
end
end
class PayloadOrderItem
attr_reader :sku, :qty, :text
def initialize(sku:, text:, qty: nil)
@sku = sku
@qty = qty
@text = text
end
def self.from_hash(data)
new(
sku: data['@sku'],
qty: data['@qty'],
text: data['#text']
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'@sku' => @sku,
'@qty' => @qty,
'#text' => @text
}
end
def to_json(*args)
to_h.to_json(*args)
end
endqty는 첫 item에만 있어 PayloadOrderItem에서 qty: nil인 선택 키워드가 됩니다. order 클래스는 item을 PayloadOrderItem.from_hash로 변환합니다.
<doc xml:lang="en" data-id="5"><title>Hi</title></doc>require "json"
class Payload
attr_reader :doc
def initialize(doc:)
@doc = doc
end
def self.from_hash(data)
new(
doc: PayloadDoc.from_hash(data['doc'])
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'doc' => @doc.to_h
}
end
def to_json(*args)
to_h.to_json(*args)
end
end
class PayloadDoc
attr_reader :xml_lang, :data_id, :title
def initialize(xml_lang:, data_id:, title:)
@xml_lang = xml_lang
@data_id = data_id
@title = title
end
def self.from_hash(data)
new(
xml_lang: data['@xml:lang'],
data_id: data['@data-id'],
title: data['title']
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'@xml:lang' => @xml_lang,
'@data-id' => @data_id,
'title' => @title
}
end
def to_json(*args)
to_h.to_json(*args)
end
end리더는 xml_lang, data_id이고 해시 키는 from_hash와 to_h 모두에서 '@xml:lang', '@data-id'입니다.
<cart><item id="1">Pen</item></cart>require "json"
class Payload
attr_reader :cart
def initialize(cart:)
@cart = cart
end
def self.from_hash(data)
new(
cart: PayloadCart.from_hash(data['cart'])
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'cart' => @cart.to_h
}
end
def to_json(*args)
to_h.to_json(*args)
end
end
class PayloadCart
attr_reader :item
def initialize(item:)
@item = item
end
def self.from_hash(data)
new(
item: data['item'].map { |v| PayloadCartItem.from_hash(v) }
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'item' => @item.map { |v| v.to_h }
}
end
def to_json(*args)
to_h.to_json(*args)
end
end
class PayloadCartItem
attr_reader :id, :text
def initialize(id:, text:)
@id = id
@text = text
end
def self.from_hash(data)
new(
id: data['@id'],
text: data['#text']
)
end
def self.from_json(json)
from_hash(JSON.parse(json))
end
def to_h
{
'@id' => @id,
'#text' => @text
}
end
def to_json(*args)
to_h.to_json(*args)
end
end옵션을 켜면 item이 하나여도 리스트이므로 from_hash는 해시 하나로 다루지 않고 data['item']을 map으로 변환합니다.
자주 하는 실수
JSON 키는 문자열입니다
from_hash는 문자열 키를 읽습니다.JSON.parse(json, symbolize_names: true)를 쓰면 키가 심볼이 되어 모든 조회가nil을 돌려줍니다.타입 검사가 전혀 없습니다
클래스는 값을 검증하거나 변환하지 않습니다. 문자열로 온
qty는 문자열 그대로여서, 생성된 코드는 타입 변화를 잡지 못합니다.Nokogiri는 다른 입력입니다
Nokogiri 노드는
.attr,.text를 제공합니다. XML을 JSON으로 먼저 매핑한 뒤 그 해시를from_hash에 넣으세요.to_h는 선택 키를 nil로 씁니다
값이 없는 선택 키는
to_h에서nil,to_json에서null로 나옵니다. 받는 쪽이 엄격하면 직접 걸러 내세요.
자주 묻는 질문
타입이 왜 없나요?
Ruby는 동적 타입 언어이고 생성기는 RBS나 Sorbet 시그니처를 추가하지 않습니다. 타입은 매핑에서 데이터를 읽는 방식에만 영향을 줍니다.
숫자 추론이 출력을 바꾸나요?
아니요. 데이터에서 qty가 숫자 3인지 문자열 "3"인지만 정하며, Ruby 클래스는 둘 다 똑같이 읽습니다.
XML을 이 클래스로 불러오려면 어떻게 하나요?
XML to JSON 변환기로 변환된 JSON을 만든 뒤 Payload.from_json(json)을 호출하세요.
심볼 키를 쓸 수 있나요?
JSON.parse가 기본으로 문자열 키를 돌려주므로 생성기도 문자열 키를 씁니다. 심볼 키를 갖고 있다면 from_hash 전에 transform_keys(&:to_s)로 바꾸세요.
Ruby 키워드는 어떻게 되나요?
class 같은 키는 리더 이름 class_value가 되고 해시 키에는 원래 단어가 남습니다.
문서가 업로드되나요?
아니요. 파싱과 생성이 브라우저 탭 안에서 실행됩니다.
마지막 업데이트:
