MYMONDAY.ai
서비스스튜디오도구데이터·포맷73CSS 생성기19색상·이미지10인코딩·보안10웹·SEO11개발·데브옵스10파일·내보내기18계산기6가이드

XML Ruby 클래스 변환기

샘플 XML에서 요소마다 해시 기반 읽기·쓰기 메서드가 있는 순수 Ruby 클래스를 만듭니다.

  • 브라우저에서 처리
  • 가입 없음
  • 무료

도구 작업 공간

예제

옵션

XML → 데이터 매핑

속성은 이 문자로 시작하는 키가 되어 id="1"은 @id가 됩니다. 비우면 속성 이름 그대로 씁니다.

속성이나 자식 요소와 함께 있는 텍스트는 이 키에 담습니다.

끄면 <item>이 하나일 땐 객체, 여러 개일 땐 배열이 됩니다. 켜면 개수와 상관없이 구조가 같습니다.

끄면 모든 값이 문자열로 남습니다. 켜면 true/false, 정수, 소수는 해당 타입이 되고 007이나 긴 ID는 문자열로 둡니다.

XML 입력

21줄491byte

결과

⌘/Ctrl + Enter로 복사
결과 · payload.rb
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, :status, :customer, :items, :total, :shipped_at, :tracking

  def initialize(id:, status:, customer:, items:, total:, shipped_at:, tracking:)
    @id = id
    @status = status
    @customer = customer
    @items = items
    @total = total
    @shipped_at = shipped_at
    @tracking = tracking
  end

  def self.from_hash(data)
    new(
      id: data['@id'],
      status: data['@status'],
      customer: PayloadOrderCustomer.from_hash(data['customer']),
      items: PayloadOrderItems.from_hash(data['items']),
      total: data['total'],
      shipped_at: data['shipped_at'],
      tracking: data['tracking']
    )
  end

  def self.from_json(json)
    from_hash(JSON.parse(json))
  end

  def to_h
    {
      '@id' => @id,
      '@status' => @status,
      'customer' => @customer.to_h,
      'items' => @items.to_h,
      'total' => @total,
      'shipped_at' => @shipped_at,
      'tracking' => @tracking
    }
  end

  def to_json(*args)
    to_h.to_json(*args)
  end
end

class PayloadOrderCustomer
  attr_reader :id, :vip, :name, :email

  def initialize(id:, vip:, name:, email:)
    @id = id
    @vip = vip
    @name = name
    @email = email
  end

  def self.from_hash(data)
    new(
      id: data['@id'],
      vip: data['@vip'],
      name: data['name'],
      email: data['email']
    )
  end

  def self.from_json(json)
    from_hash(JSON.parse(json))
  end

  def to_h
    {
      '@id' => @id,
      '@vip' => @vip,
      'name' => @name,
      'email' => @email
    }
  end

  def to_json(*args)
    to_h.to_json(*args)
  end
end

class PayloadOrderItems
  attr_reader :item

  def initialize(item:)
    @item = item
  end

  def self.from_hash(data)
    new(
      item: data['item'].map { |v| PayloadOrderItemsItem.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 PayloadOrderItemsItem
  attr_reader :sku, :qty, :name, :price

  def initialize(sku:, qty:, name:, price:)
    @sku = sku
    @qty = qty
    @name = name
    @price = price
  end

  def self.from_hash(data)
    new(
      sku: data['@sku'],
      qty: data['@qty'],
      name: data['name'],
      price: data['price']
    )
  end

  def self.from_json(json)
    from_hash(JSON.parse(json))
  end

  def to_h
    {
      '@sku' => @sku,
      '@qty' => @qty,
      'name' => @name,
      'price' => @price
    }
  end

  def to_json(*args)
    to_h.to_json(*args)
  end
end
타입5
크기2.9kB

이 변환기는 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까지 받습니다. 브라우저를 벗어나는 데이터는 없습니다.

사용 방법

  1. XML 붙여넣기

    문서를 붙여 넣거나 예제를 엽니다. 입력하는 동안 Ruby 클래스가 다시 만들어집니다.

  2. 매핑 설정

    XML 데이터 매핑 패널에서 속성 접두사와 텍스트 키를 고르고 항상 배열 여부를 정합니다. 숫자 추론은 Ruby 코드를 바꾸지 않습니다.

  3. 루트 이름 정하기

    루트 클래스 이름(기본 Payload)을 입력합니다. 중첩 클래스는 부모 이름을 접두사로 씁니다.

  4. 클래스 읽기

    각 클래스는 리더를 나열한 뒤 키워드 초기화 메서드를 갖고, 선택 키는 nil 기본값으로 맨 뒤에 옵니다.

  5. 복사 또는 다운로드

    코드를 복사하거나 루트 이름의 스네이크 케이스인 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
end

qty는 첫 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가 되고 해시 키에는 원래 단어가 남습니다.

문서가 업로드되나요?

아니요. 파싱과 생성이 브라우저 탭 안에서 실행됩니다.

마지막 업데이트: