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

JSON을 Ruby 클래스로 변환

샘플 JSON으로 attr_reader, from_hash, to_json이 있는 Ruby 클래스를 만들어 줍니다.

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

도구 작업 공간

예제

옵션

JSON 입력

27줄435byte

결과

⌘/Ctrl + Enter로 복사
결과 · payload.rb
require "json"

class Payload
  attr_reader :id, :status, :customer, :items, :total, :shipped_at, :tracking

  def initialize(id:, status:, customer:, items:, total:, shipped_at:, tracking: nil)
    @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: PayloadCustomer.from_hash(data['customer']),
      items: data['items'].map { |v| PayloadItem.from_hash(v) },
      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.map { |v| v.to_h },
      'total' => @total,
      'shipped_at' => @shipped_at,
      'tracking' => @tracking
    }
  end

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

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

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

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

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

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

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

class PayloadItem
  attr_reader :sku, :name, :qty, :price

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

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

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

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

  def to_json(*args)
    to_h.to_json(*args)
  end
end
타입3
크기2.1kB

JSON to Ruby 변환기는 샘플 JSON을 읽어 객체 모양마다 클래스 하나를 만듭니다. 각 클래스에는 attr_reader, 키워드 인자를 받는 initialize, self.from_hash, self.from_json, to_h, to_json이 들어 있습니다. 표준 json 라이브러리만 쓰므로 별도 gem이 필요 없습니다.

같은 위치에 있는 객체는 합쳐집니다. 객체 배열이면 모든 원소의 키를 합친 클래스가 만들어지고, 일부 원소에 없거나 일부에서 null인 키는 기본값이 nil인 선택 키워드 인자가 되어 필수 인자 뒤에 놓입니다. 루트는 객체와 배열 모두 가능하고 "루트 타입 이름" 옵션(기본값 Payload)이 루트 상수 이름을 정합니다.

입력은 브라우저 안에서 처리되며 어디에도 전송되지 않습니다. 한도는 20 MiB입니다. Ruby는 동적 언어라서 생성된 클래스는 모양을 문서화할 뿐 타입을 검사하지 않고, 샘플 하나로는 키가 항상 있다는 것을 증명할 수 없습니다.

사용 방법

  1. JSON 붙여넣기

    샘플 데이터를 붙여넣거나 내장 샘플을 불러옵니다. 최상위 객체와 배열은 받지만 문자열, 숫자, 불리언 단일 값은 거부됩니다.

  2. 루트 클래스 이름 지정

    Payload 대신 다른 루트 상수를 쓰려면 "루트 타입 이름"을 바꿉니다. PascalCase로 변환됩니다.

  3. 변환 실행

    변환을 실행합니다. 잘못된 JSON이나 빈 입력은 오류를 보여 줍니다.

  4. 결과 확인

    파일은 require "json"으로 시작합니다. 루트 클래스가 먼저 나오고 중첩 클래스가 뒤따릅니다. 배열 루트라면 # The root JSON value is an array of PayloadItem 같은 주석이 루트를 대신합니다.

  5. 복사 또는 다운로드

    코드를 복사하거나 .rb 파일로 저장한 뒤 require_relative로 불러옵니다.

작동 원리

값을 읽고 쓰는 방식

from_hash는 문자열 키를 가진 해시를 받습니다. JSON.parse의 기본 결과가 바로 그 형태입니다. 심볼 키는 받지 않으므로 symbolize_names: true를 쓰기 전에 호출하거나 키를 먼저 바꿔야 합니다.

스칼라는 data['id']처럼 그대로 넘기므로 스칼라 타입은 강제되지 않습니다. 객체는 Child.from_hash로, 객체 리스트는 중첩 단계마다 map으로 다시 만듭니다. 값이 null이거나 없을 수 있으면 nil에 안전한 코드가 나옵니다. 객체는 data['u'] && Child.from_hash(data['u']), 리스트는 data['rows']&.map { ... }입니다.

JSON 값Ruby 처리생성되는 읽기 코드
문자열, 숫자, 불리언그대로 전달data['key']
null, 또는 일부 객체에 없는 키선택 키워드, 기본값 nildata['key']
객체중첩 클래스Child.from_hash(data['key'])
null이거나 없을 수 있는 객체중첩 클래스 또는 nildata['key'] && Child.from_hash(data['key'])
객체 리스트중첩 클래스의 배열data['key'].map { |v| Child.from_hash(v) }
객체 리스트의 리스트중첩 mapdata['key'].map { |v| v.map { |v1| Child.from_hash(v1) } }
스칼라 리스트, 빈 리스트, 섞인 리스트배열 그대로data['key']

이름 규칙과 예약어

클래스 이름은 키 경로로 만든 PascalCase 상수이며 PayloadUser 같은 형태입니다. 리스트 원소 클래스는 키의 단수형을 따라 items는 PayloadItem, categories는 PayloadCategory, data는 PayloadDataItem이 됩니다. 핵심 상수(Class, Module, Object, BasicObject, Kernel, String, Array, Hash, Integer, Float, Symbol, Comparable, Enumerable, Struct, Time, File, JSON)와 겹치는 이름에는 Data 접미사가 붙고, 이름이 겹치면 2부터 숫자가 붙습니다.

속성 이름은 ASCII 영문자와 숫자로 만든 snake_case입니다. camelCase 경계와 그 밖의 문자는 밑줄이 되어 userName은 user_name이 됩니다. 숫자로 시작하면 field_ 접두사가 붙어 field_2fa가 됩니다. Ruby 키워드, 생성 메서드(initialize, from_hash, from_json, to_h, to_json), 상속 메서드(hash, method, send, object_id, freeze, dup, clone, display)에는 _value 접미사가 붙어 class_value나 hash_value가 됩니다. 원래 문자열 키는 to_h 매핑에 그대로 남습니다.

userName -> user_name
class    -> class_value
2fa      -> field_2fa

JSON 왕복 변환

Payload.from_json(문자열)은 파싱하고 객체를 구성하며, to_h는 원래 문자열 키를 가진 해시를 돌려주고, to_json(*args)는 to_h.to_json(*args)를 호출합니다. 따라서 payload.to_json과 JSON.generate(payload)의 결과에는 snake_case 속성 이름이 아니라 원래 키 이름이 나옵니다.

선택 키는 nil로 돌아옵니다. 반면 필수 키가 없으면 다릅니다. 필수 객체나 리스트가 없을 때는 nil에 from_hash나 map을 호출하므로 from_hash 안에서 NoMethodError가 납니다. 샘플 객체 일부에서 빠진 키는 선택으로 생성되므로 이 문제를 피할 수 있습니다.

실제 예시

중첩 객체와 리스트
입력
{"id":42,"user":{"name":"Ada","active":true},"roles":["admin","editor"]}
출력
require "json"

class Payload
  attr_reader :id, :user, :roles

  def initialize(id:, user:, roles:)
    @id = id
    @user = user
    @roles = roles
  end

  def self.from_hash(data)
    new(
      id: data['id'],
      user: PayloadUser.from_hash(data['user']),
      roles: data['roles']
    )
  end

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

  def to_h
    {
      'id' => @id,
      'user' => @user.to_h,
      'roles' => @roles
    }
  end

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

class PayloadUser
  attr_reader :name, :active

  def initialize(name:, active:)
    @name = name
    @active = active
  end

  def self.from_hash(data)
    new(
      name: data['name'],
      active: data['active']
    )
  end

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

  def to_h
    {
      'name' => @name,
      'active' => @active
    }
  end

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

키워드 인자 생성자를 가진 일반 Ruby 클래스 두 개가 루트부터 나옵니다. 중첩 객체는 PayloadUser.from_hash로 다시 만들어지고 문자열 리스트는 그대로 전달됩니다.

snake_case, 예약어, 합쳐진 객체 리스트
입력
{"user_id":7,"userName":"Ada","class":"admin","2fa":true,"note":null,"items":[{"sku":"A1"},{"sku":"B2","qty":3}]}
출력
require "json"

class Payload
  attr_reader :user_id, :user_name, :class_value, :field_2fa, :note, :items

  def initialize(user_id:, user_name:, class_value:, field_2fa:, items:, note: nil)
    @user_id = user_id
    @user_name = user_name
    @class_value = class_value
    @field_2fa = field_2fa
    @note = note
    @items = items
  end

  def self.from_hash(data)
    new(
      user_id: data['user_id'],
      user_name: data['userName'],
      class_value: data['class'],
      field_2fa: data['2fa'],
      note: data['note'],
      items: data['items'].map { |v| PayloadItem.from_hash(v) }
    )
  end

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

  def to_h
    {
      'user_id' => @user_id,
      'userName' => @user_name,
      'class' => @class_value,
      '2fa' => @field_2fa,
      'note' => @note,
      'items' => @items.map { |v| v.to_h }
    }
  end

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

class PayloadItem
  attr_reader :sku, :qty

  def initialize(sku:, qty: nil)
    @sku = sku
    @qty = qty
  end

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

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

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

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

userName은 user_name, class는 class_value, 2fa는 field_2fa가 되고 null만 있는 note는 선택 키워드가 됩니다. 두 항목은 PayloadItem으로 합쳐지며 qty는 한 원소에만 있어 선택입니다.

빈 리스트, 섞인 숫자, 빈 객체
입력
{"tags":[],"scores":[1,2.5],"meta":{}}
출력
require "json"

class Payload
  attr_reader :tags, :scores, :meta

  def initialize(tags:, scores:, meta:)
    @tags = tags
    @scores = scores
    @meta = meta
  end

  def self.from_hash(data)
    new(
      tags: data['tags'],
      scores: data['scores'],
      meta: PayloadMeta.from_hash(data['meta'])
    )
  end

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

  def to_h
    {
      'tags' => @tags,
      'scores' => @scores,
      'meta' => @meta.to_h
    }
  end

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

class PayloadMeta
  def initialize()
  end

  def self.from_hash(data)
    new
  end

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

  def to_h
    {
    }
  end

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

스칼라 리스트와 빈 리스트는 그대로 전달되고, 빈 객체도 속성이 없는 클래스가 됩니다.

널 허용 중첩 객체가 있는 최상위 배열
입력
[{"id":1,"profile":{"city":"Seoul"}},{"id":2,"profile":null}]
출력
require "json"

# The root JSON value is an array of PayloadItem

class PayloadItem
  attr_reader :id, :profile

  def initialize(id:, profile: nil)
    @id = id
    @profile = profile
  end

  def self.from_hash(data)
    new(
      id: data['id'],
      profile: data['profile'] && PayloadItemProfile.from_hash(data['profile'])
    )
  end

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

  def to_h
    {
      'id' => @id,
      'profile' => @profile&.to_h
    }
  end

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

class PayloadItemProfile
  attr_reader :city

  def initialize(city:)
    @city = city
  end

  def self.from_hash(data)
    new(
      city: data['city']
    )
  end

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

  def to_h
    {
      'city' => @city
    }
  end

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

배열 루트에는 클래스가 따로 없고 주석이 PayloadItem을 알려 줍니다. profile이 한 원소에서 null이므로 선택 키워드가 되어 data['profile'] && ...로 다시 만들어지고 &.to_h로 쓰입니다.

자주 하는 실수

  • 샘플 하나로는 키가 항상 있다고 증명할 수 없습니다

    붙여넣은 모든 원소에 있는 키는 필수 키워드 인자가 되고, 필수 객체나 리스트가 없으면 from_hash에서 NoMethodError가 납니다. 선택 키가 빠진 레코드를 포함해 여러 건을 붙여넣어 그 키들이 선택이 되게 하세요.

  • null만 있는 키는 타입 정보가 없습니다

    붙여넣은 모든 객체에서 note가 null이면 무엇이 들어오는지 알 수 없습니다. 선택 속성으로 생성되어 값이 그대로 전달되는데, Ruby에서는 문제없지만 읽는 사람에게는 정보가 없습니다.

  • 심볼 키는 동작하지 않습니다

    from_hash는 문자열로 인덱싱합니다. JSON.parse(json, symbolize_names: true)로 만든 해시를 넘기면 전부 nil이 되고, 필수 객체에서는 NoMethodError가 납니다.

  • 타입 검사가 없습니다

    샘플에서 1과 2.5가 float로 합쳐졌더라도 price 속성이 숫자임을 강제하지 않습니다. 생성된 클래스는 모양을 문서화할 뿐 검증하지 않습니다.

  • 샘플에 없던 null 항목

    샘플에서 null이던 원소에만 nil 방어 코드가 붙습니다. 샘플에 없던 null 원소가 실제 데이터에 있으면 from_hash에서 NoMethodError가 납니다.

  • 2^53을 넘는 정수는 이미 반올림되어 있습니다

    타입 추론은 JSON 숫자를 JavaScript number 정밀도로 저장하므로 9007199254740993은 코드 생성 전에 자릿수를 잃을 수 있습니다. Ruby 정수는 크기 제한이 없지만 원래 자릿수는 이미 복원할 수 없습니다.

자주 묻는 질문

Struct나 Data.define 클래스를 만들어 주나요?

아니요. attr_reader와 키워드 인자를 쓰는 일반 클래스를 만듭니다. 불변 객체가 필요하면 Ruby 3.2 이상에서 Data.define으로 직접 바꾸세요.

심볼 키와 함께 쓸 수 있나요?

바로는 안 됩니다. 기본 문자열 키로 파싱하거나 transform_keys(&:to_s)로 먼저 변환하세요.

JSON 배열을 붙여넣어도 되나요?

네. 원소들이 PayloadItem 같은 클래스 하나로 합쳐지고 주석이 배열 루트를 알려 줍니다. 원소마다 PayloadItem.from_hash로 읽으면 됩니다.

선택 키는 어떻게 처리되나요?

일부 객체에 없거나 일부에서 null인 키는 기본값 nil인 키워드 인자가 되어 필수 인자 뒤에 놓입니다. 중첩 객체와 리스트는 nil에 안전한 코드로 처리됩니다.

gem이 필요한가요?

아니요. 표준 json 라이브러리만 필요합니다.

속성 이름이 class_value로 나오는 이유는 무엇인가요?

class는 Ruby 키워드이자 기존 메서드 이름이라서 _value 접미사를 붙이고 to_h에는 'class' 키를 그대로 둡니다.

숫자는 어떻게 처리되나요?

그대로 전달됩니다. Integer나 Float 검사 코드는 쓰지 않으며, 한 위치의 정수와 소수가 합쳐지는 것은 내부 추론에서만 일어납니다.

JSON이 업로드되나요?

아니요. 변환은 브라우저 안에서 실행됩니다.

마지막 업데이트: