NV ToolkitFormat · Validate · Convert

Code Generators

JSON to Ruby Converter

Paste a JSON or XML sample — the format is auto-detected — and get typed Ruby models with names, nesting and nullability inferred for you.

In short

Convert JSON to Ruby online: paste a sample and generate typed Ruby models with serialization helpers — powered by quicktype, in your browser.

By NaveenKumar T · Updated

Learn: JSON Examples: From Hello World to Real API Payloads

  • JSON or XML input, auto-detected
  • Nested objects become types
  • Nullability inferred

Runs entirely in your browser — nothing you paste or open here is uploaded, logged, or stored.How we handle data →

Example

Example: JSON order payload to Ruby

A small order document with a nested object, an array of two items, a decimal total and a null field — enough to show how each Ruby type, list and nullable is inferred. The same page accepts the XML form of this order too, so the sample below is an example rather than the only accepted format. The right-hand side is the exact output of this tool.

Input · JSON
{
  "id": "ord_9F2KX",
  "status": "processing",
  "paid": true,
  "customer": { "name": "Ada Lovelace", "email": "ada@example.com", "phone": null },
  "items": [
    { "sku": "KB-75", "qty": 1, "price": 149.99 },
    { "sku": "MS-12", "qty": 2, "price": 39.5 }
  ],
  "total": 228.99
}
Generated · Ruby
# This code may look unusually verbose for Ruby (and it is), but
# it performs some subtle and complex validation of JSON data.
#
# To parse this JSON, add 'dry-struct' and 'dry-types' gems, then do:
#
#   root = Root.from_json! "{…}"
#   puts root.items.first.sku
#
# If from_json! succeeds, the value returned matches the schema.

require 'json'
require 'dry-types'
require 'dry-struct'

module Types
  include Dry.Types(default: :nominal)

  Integer = Strict::Integer
  Nil     = Strict::Nil
  Bool    = Strict::Bool
  Hash    = Strict::Hash
  String  = Strict::String
  Double  = Strict::Float | Strict::Integer
end

class Customer < Dry::Struct
  attribute :customer_name, Types::String
  attribute :email,         Types::String
  attribute :phone,         Types::Nil

  def self.from_dynamic!(d)
    d = Types::Hash[d]
    new(
      customer_name: d.fetch("name"),
      email:         d.fetch("email"),
      phone:         d.fetch("phone"),
    )
  end

  def self.from_json!(json)
    from_dynamic!(JSON.parse(json))
  end

  def to_dynamic
    {
      "name"  => customer_name,
      "email" => email,
      "phone" => phone,
    }
  end

  def to_json(options = nil)
    JSON.generate(to_dynamic, options)
  end
end

class Item < Dry::Struct
  attribute :sku,   Types::String
  attribute :qty,   Types::Integer
  attribute :price, Types::Double

  def self.from_dynamic!(d)
    d = Types::Hash[d]
    new(
      sku:   d.fetch("sku"),
      qty:   d.fetch("qty"),
      price: d.fetch("price"),
    )
  end

  def self.from_json!(json)
    from_dynamic!(JSON.parse(json))
  end

  def to_dynamic
    {
      "sku"   => sku,
      "qty"   => qty,
      "price" => price,
    }
  end

  def to_json(options = nil)
    JSON.generate(to_dynamic, options)
  end
end

class Root < Dry::Struct
  attribute :id,       Types::String
  attribute :status,   Types::String
  attribute :paid,     Types::Bool
  attribute :customer, Customer
  attribute :items,    Types.Array(Item)
  attribute :total,    Types::Double

  def self.from_dynamic!(d)
    d = Types::Hash[d]
    new(
      id:       d.fetch("id"),
      status:   d.fetch("status"),
      paid:     d.fetch("paid"),
      customer: Customer.from_dynamic!(d.fetch("customer")),
      items:    d.fetch("items").map { |x| Item.from_dynamic!(x) },
      total:    d.fetch("total"),
    )
  end

  def self.from_json!(json)
    from_dynamic!(JSON.parse(json))
  end

  def to_dynamic
    {
      "id"       => id,
      "status"   => status,
      "paid"     => paid,
      "customer" => customer.to_dynamic,
      "items"    => items.map { |x| x.to_dynamic },
      "total"    => total,
    }
  end

  def to_json(options = nil)
    JSON.generate(to_dynamic, options)
  end
end

Look for the "phone" field: sampled as null, it is typed as Types::Nil. "items" became Types.Array(Item), and "total" is Types::Double.

Learn more

From JSON sample to Ruby types

Hand-writing model classes from a JSON payload is transcription work: read a field, type a property, repeat eighty times, miss one. This page runs quicktype (the same engine behind the best-known generators) against your document, merging array elements to find the common shape and naming nested types from their keys.

from_json! raises Dry::Struct::Error on the first attribute that does not match, and the strict Types aliases mean nil for a required field raises too. Wrap the call and log e.message, which names the failing attribute; avoid rescuing StandardError broadly, or the payload drift the strict types exist to surface gets silently swallowed.

How JSON values become Ruby types

Inference works from the values in your sample. Strings map to Types::String, integers and decimals are told apart (228.99 becomes Types::Double, while 1 and 2 stay integral), true/false become the language's boolean, and an object such as "customer" becomes its own named type. The "items" array is typed as Types.Array(Item) because every element shares the same keys — quicktype unifies the shapes of all array elements into one Item type.

Nulls are the one place inference needs your help: "phone": null carries no type information, so the field is emitted as Types::Nil. If the real API sometimes sends a string there, include a sample where the field is populated (paste several documents merged into one array) and the generated type becomes the union of what it saw.

Pasting XML instead: how elements and attributes become Ruby types

The XML is parsed into a data model before inference (attributes included, text nodes named "text"), so the root element becomes a nested type: <order> is a field of type Order on the top-level Root rather than the top level itself. Child elements become fields, and repeated elements — the two <item> siblings — are collected into Types.Array(Item) under an Items wrapper type that mirrors the <items> container.

Attributes and element text are typed differently, and this matters: element text is type-inferred, so <total>228.99</total> becomes Types::Double, while attribute values are kept as they were written — qty="1" and price="149.99" stay Types::String in Ruby. Prefer element text for numeric data you want typed, or cast attribute values after decoding. One more trap: a single <item> would not be recognized as a list, so make sure repeated elements appear at least twice in the sample.

Annotations, nulls and nested arrays in the generated Ruby

Models are Dry::Struct classes built from attribute declarations, backed by a generated Types module of strict aliases where Double is a Float-or-Integer union. There are no rename annotations: the wire key is written out in from_dynamic! (d.fetch("customer-ref")) and again in to_dynamic, so an attribute can be renamed safely. Quicktype does rename when it must — a name key inside Customer becomes customer_name to avoid colliding with Ruby's own name method, while to_dynamic still writes the original key back out.

A field sampled only as null is typed Types::Nil, which accepts nothing but nil, so from_json! raises the moment the API sends a string. Merge a document with a value and it becomes a String type with .optional appended — the union you want. Keys missing from some samples are read with d["key"] instead of d.fetch("key"), so absence is tolerated rather than fatal. Because every alias is strict, from_json! validates the whole tree eagerly: if it returns, the payload matched the declared attributes.

Nested objects become their own Dry::Struct classes named from the key — Customer, Item — and the parent's from_dynamic! calls the child's explicitly rather than trusting a hash. Arrays of objects are typed with Types.Array around the element class and built by mapping from_dynamic! over the array, so every element is validated. Each class also gets to_dynamic (a plain hash) and to_json. An empty array degrades to an array of Types::Any, and a one-element array cannot show which attributes are optional.

Help

Frequently asked questions

Everything you need to know about the JSON to Ruby.

01How do I generate Ruby classes from JSON?

Paste a representative document — JSON or XML, the format is detected automatically — and press Convert. A real API response works best. Nested objects become their own Ruby types, arrays become Types.Array(Item), and the result downloads as Root.rb.

02What does the generated Ruby code include?

Ruby output uses Dry::Struct with typed attributes — structured, validated models rather than raw hashes, generated with the types your payload implies.

03How do I use the generated Ruby code in my project?

Add the dry-struct and dry-types gems, require the file, and call Root.from_json!(string) — it raises if the JSON does not match the declared attributes. Every struct also has to_json and a to_dynamic method that returns a plain hash for further processing.

04What does the generated Ruby depend on?

The dry-struct and dry-types gems — the mainstream choice for typed Ruby models. Prefer plain Structs or hashes? The attribute list generated here is still the map you'd hand-write.

Keep working

Related tools

Learn the format

Related guides

All guides →