HomeJSON ConvertersJSON to Ruby Class Generator

JSON to Ruby Class Generator

Convert any JSON object into Ruby classes with attr_accessor declarations, an initialize method that maps JSON keys to Ruby attributes, camelCase to snake_case field name conversion, nested class decomposition, and a from_json class method — paste directly into your Ruby or Rails project.

Convert any JSON object into Ruby classes with attr_accessor declarations, an initialize method that maps JSON keys to Ruby attributes, camelCase to snake_case field name conversion, nested class decomposition, and a from_json class method — paste directly into your Ruby or Rails project.

This tool is designed to provide a seamless experience for developers by handling complex operations directly in your browser with maximum speed and security.

100% Private
Instant Results
Customizable
Offline Ready
Dev-Friendly
Easy Export

Ruby's JSON library parses JSON strings into plain Ruby hashes and arrays. JSON.parse('{"name":"Alice","age":30}') returns {"name"=>"Alice","age"=>30} — a Hash object with string keys. You can work with API responses directly as hashes: data["name"], data.fetch("age"). For small scripts, simple data extraction, or temporary data processing pipelines, plain hashes are perfectly fine. The friction comes when you start passing the hash around between methods, because hash["name"] is fragile — a typo in the key string silently returns nil, there is no autocomplete, and the data structure is invisible from the function signature. A class with defined attributes makes the structure explicit and the typos compile-time-visible.

A Plain Old Ruby Object (PORO) is the simplest form of a Ruby model class — a class with attr_accessor declarations for each field and an initialize method that populates them from a hash. The attr_accessor macro generates a getter and setter method for each named attribute. A class User with attr_accessor :name, :email gives you user.name and user.name= without writing those methods manually. For JSON data, you add an initialize that maps the parsed hash keys to the instance attributes. The convention in Ruby is snake_case for attribute names — is_active, created_at, user_name — which means camelCase JSON keys (isActive, createdAt, userName) need to be converted during initialization.

Ruby also has several alternatives to hand-written POROs for JSON data. Struct creates an immutable value object with defined members: UserStruct = Struct.new(:name, :email, :age). OpenStruct maps hash keys to methods dynamically without defining a class in advance — useful for quick prototyping but problematic for production because it hides structure and has performance issues in Ruby 3.x. The dry-struct gem provides typed structs with coercion, similar to TypeScript interfaces with runtime validation. For Rails applications, ActiveModel::Model gives plain classes access to validations, serialization, and form helpers. The right choice depends on how much structure, immutability, and validation you need — this tool generates a practical PORO starting point that you can extend or replace with any of these alternatives.

Give it a JSON object and it generates a Ruby class with attr_accessor declarations for every field, an initialize(hash) method that populates each attribute from the hash, and a self.from_json(json_string) class method that parses the JSON string and calls initialize. JSON field names are converted from camelCase to snake_case following Ruby naming conventions — isActive becomes is_active, createdAt becomes created_at, userName becomes user_name. The JSON hash key (the original camelCase string) is used in the initialize method to read from the parsed hash, and the Ruby snake_case name is used for the attr_accessor and as the attribute name in your code. For nested JSON objects, the tool generates a separate Ruby class for each nesting level and initializes nested attributes using the nested class's constructor. A user JSON object with a nested profile object generates a Profile class and sets @profile = Profile.new(hash["profile"]) in the User initializer. Arrays of primitive values (strings, numbers) are assigned directly. Arrays of objects generate a map: @items = (hash["items"] || []).map { |item| Item.new(item) }. The generated code is dependency-free plain Ruby — no gems required, no Rails dependency. It works in a vanilla Ruby script, a Sinatra app, a Rails model layer, or a Ruby gem. The from_json class method handles the JSON.parse call and requires the json standard library (require 'json') which is included in the Ruby standard library and available in all Ruby environments. After generating, you typically rename the top-level class, decide whether you want attr_reader instead of attr_accessor for immutability, and add any validations or methods your domain requires.

1. Paste your JSON into the Input JSON field — use a real API response sample that includes all the fields your code will access. If the response sometimes omits fields or has nullable values, include a sample with those fields present so the tool can infer the attribute names correctly. Click Load Example to see a sample nested structure before using your own data.

2. Click Convert to Ruby Hash — the tool parses every field, converts camelCase keys to snake_case attribute names, generates the class with attr_accessor declarations and initialize method, creates nested class definitions for nested objects, and outputs the complete Ruby code with the from_json class method.

3. Rename the generated class to a meaningful domain name — the auto-generated class name is a placeholder. Change it to User, Product, WebhookPayload, GithubRepo, or whatever the data represents. Nested class names should also be renamed to reflect the domain: Profile, Address, LineItem rather than NestedObject.

4. Decide between attr_accessor and attr_reader — the generated code uses attr_accessor which creates both getter and setter methods, making attributes mutable. If you want the object to be immutable after initialization (which is often the right choice for data objects representing API responses), change attr_accessor to attr_reader. Immutable objects are easier to reason about and prevent accidental mutation in downstream code.

5. Add the generated class to your Ruby or Rails project — in a vanilla Ruby script, require 'json' at the top and paste the class. In Rails, put the class in app/models/ or app/services/ depending on your project structure. Use it: user = User.from_json(api_response_body). Access attributes with user.name, user.email, user.profile.bio. Nested objects are accessible through chained attribute calls.

The hash vs class question is something every Ruby developer working with APIs answers repeatedly. Early in a project it is always a hash — fast, simple, no overhead. Then the hash starts getting passed to five different methods. Then someone adds response["user_name"] when the key is actually "userName" and it returns nil and no one notices for a week because the nil propagates into a template that handles nil gracefully. Then you write three methods that accept "the user hash" and all three have slightly different assumptions about which keys are present. At some point the hash becomes a class, and the question is just whether you do it before or after the first confusing nil bug. This tool is most useful at the start of a new API integration, before any hash-passing habits have formed. You get the API response sample, generate the class, and start using response.user_name instead of response["userName"] from day one. The camelCase to snake_case conversion is handled automatically — one of the tedious parts of writing these classes by hand is tracking which attribute names map to which JSON keys, especially for a response with 20 fields from an API that uses camelCase. In Rails specifically, JSON serialization comes up in three scenarios where PORO models are valuable: API-only Rails apps that receive JSON request bodies and need to parse them into something more structured than params.permit(), service objects that call external APIs and need to wrap the response in a typed object before passing it to the domain layer, and background job payloads where the job receives serialized arguments and deserializes them into structured objects. In all three cases, a generated class scaffold saves the boilerplate and lets you focus on the domain logic that actually varies between implementations.

camelCase to snake_case conversion — JSON field names using camelCase are automatically converted to Ruby snake_case attribute names while preserving the original JSON key in the initialize hash lookup

attr_accessor and initialize included — generates the complete class structure including attribute declarations and the hash-to-attributes mapping so no boilerplate needs to be written manually

from_json class method — includes a self.from_json(json_string) class method that handles JSON.parse so callers can pass raw JSON strings directly

Nested class decomposition — generates separate Ruby class definitions for nested JSON objects with nil guards (if hash["field"]) for nullable nested objects

Safe array initialization — array fields use || [] to default to an empty array when the key is absent or nil preventing NoMethodError on missing array fields

Dependency-free plain Ruby — the generated code only requires the json standard library which is part of Ruby's standard library and needs no gem installation

100% browser-based — your JSON API response data never leaves your machine whether the responses contain user data internal endpoint paths or proprietary data models

Instant generation — all field mapping and code generation runs in your browser with no round-trip

Wrapping third-party API responses in typed Ruby objects instead of passing raw hashes between service objects

Creating PORO models for Rails API-only applications that receive JSON request bodies and need structured attribute access

Building typed data objects for Sidekiq or Resque background job payloads that are serialized and deserialized as JSON

Generating Ruby class scaffolds when integrating with external services like Stripe GitHub Shopify or Twilio that return JSON responses

Creating data transfer objects for Rails service objects that call external APIs and pass structured responses to the domain layer

Building immutable value objects from webhook payloads for event-driven Ruby applications

Generating Ruby classes for data extraction scripts that parse JSON files or API responses and need structured attribute access

Learning Ruby data modeling patterns by seeing how JSON structures map to Ruby class definitions with initialize and attr_accessor

Example Input

{
  "id": 1,
  "userName": "priya_singh",
  "email": "priya@learnhubly.com",
  "isActive": true,
  "score": 98.5,
  "tags": ["developer", "admin"],
  "profile": {
    "bio": "Principal Software Engineer",
    "skills": ["Go", "React", "TypeScript"]
  },
  "deletedAt": null
}

Example Output

require 'json'

class Profile
  attr_accessor :bio, :skills

  def initialize(hash)
    @bio = hash["bio"]
    @skills = hash["skills"] || []
  end

  def self.from_json(json_string)
    new(JSON.parse(json_string))
  end
end

class User
  attr_accessor :id, :user_name, :email, :is_active, :score, :tags, :profile, :deleted_at

  def initialize(hash)
    @id = hash["id"]
    @user_name = hash["userName"]
    @email = hash["email"]
    @is_active = hash["isActive"]
    @score = hash["score"]
    @tags = hash["tags"] || []
    @profile = Profile.new(hash["profile"]) if hash["profile"]
    @deleted_at = hash["deletedAt"]
  end

  def self.from_json(json_string)
    new(JSON.parse(json_string))
  end
end

# Usage:
# user = User.from_json(api_response_body)
# user.user_name    # => "priya_singh"
# user.profile.bio  # => "Principal Software Engineer"
# user.is_active    # => true

Naming Conventions — camelCase Keys in Generated Attribute Names: The tool converts camelCase JSON keys to snake_case Ruby attribute names. isActive becomes is_active, createdAt becomes created_at, firstName becomes first_name. The initialize method uses the original JSON key ('isActive') to read from the parsed hash, and the attr_accessor uses the snake_case name (is_active) as the Ruby attribute. If you manually edit the generated initialize and use the snake_case name as the hash key, the attribute will always be nil because JSON.parse preserves the original camelCase keys from the JSON.

Invalid JSON Input: The tool requires valid JSON to generate Ruby classes. Syntax errors — missing commas, unquoted keys, trailing commas, Ruby-style symbol keys (:name) — will prevent generation. JSON uses string keys and double quotes. Use the JSON Formatter and Validator tool to fix any syntax errors first.

Nested Object Is Nil at Runtime: The generated initialize uses if hash['profile'] to guard against the nested hash being nil before calling Profile.new. If you manually edit the generated code and remove this guard, calling Profile.new(nil) raises a NoMethodError when nil is passed to the nested class initializer. Keep the nil guards in place for any nested object that might be absent from the API response. For objects that should always be present, you can remove the guard and let the NoMethodError surface early as a signal that the API returned an unexpected shape.

Arrays of Primitive Values vs Arrays of Objects: The tool generates @tags = hash['tags'] || [] for arrays of primitive values (strings, numbers). For arrays of objects, it generates a map: @items = (hash['items'] || []).map { |item| Item.new(item) }. If you edit the generated code and accidentally apply the map pattern to a primitive array, you will get an error because strings and numbers do not have a new class method. Verify whether each array field contains primitives or objects before modifying the generated array handling.

JSON.parse Returns String Keys, Not Symbol Keys: Ruby developers familiar with Rails' HashWithIndifferentAccess or the hash.to_sym pattern sometimes try to access parsed JSON with symbol keys: hash[:name]. JSON.parse returns string keys by default — hash['name']. The generated initialize uses string keys throughout. If you call JSON.parse(json_string, symbolize_names: true), the keys become symbols and all the hash['field'] lookups in the generated initialize will return nil. Either use string keys (JSON.parse without symbolize_names) or update all the hash lookups to use symbol keys consistently.

Using attr_accessor when attr_reader would be more appropriate for data objects

Fix: attr_accessor generates both a getter (object.name) and a setter (object.name = 'value'). For objects representing API responses — data you receive from an external system and parse once — setters are usually not needed and can lead to accidental mutation. A method somewhere in your codebase sets user.email = nil thinking it is a local variable, and the original response object is silently corrupted. attr_reader generates only the getter, making the object effectively immutable after initialization. This is the right choice for value objects and data transfer objects. Change attr_accessor to attr_reader in the generated class unless you specifically need to modify the attributes after construction.

Passing a raw API response string to User.new instead of User.from_json

Fix: The generated class has two entry points: User.new(hash) expects a pre-parsed Ruby hash, and User.from_json(json_string) expects a raw JSON string and handles JSON.parse internally. Passing a JSON string to User.new will not raise an error immediately — Ruby will accept the string as the hash argument — but every attribute will be set by calling string["field_name"] on the JSON string, which returns nil for a string subscript that is not an integer. All attributes will silently be nil. Use User.from_json(response_body) when you have a raw JSON string. Use User.new(parsed_hash) when you have already called JSON.parse yourself.

Not adding validation and treating the generated class as production-ready without review

Fix: The generated class maps fields to attributes. It has no validation — an invalid email, a negative price, a missing required field — these all pass through the initializer without any error. For production code, consider what invariants your objects must satisfy and add validation accordingly. In plain Ruby, add a validate method that raises ArgumentError or returns false if required fields are nil or values are out of range. In Rails, include ActiveModel::Validations and add validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }. For data transfer objects that just need to expose API data, validation may not be needed — but for domain objects that drive business logic, it almost always is.

Generating a class for data that should just be a hash or Struct

Fix: Not every JSON response needs a full PORO. A configuration object you parse once at startup and pass to two methods: use a plain hash or a Struct. A response that you immediately deconstruct into local variables and never pass around: use hash directly. A simple value object with 3-4 fields and no methods: use Struct. The generated PORO class is right when you need named attribute access, when you will pass the object through multiple layers of your application, when you want to add behavior (methods) to the data, or when the data structure is complex enough that a class provides meaningful clarity. For simple cases, the hash from JSON.parse is the right tool and adding a class layer is unnecessary complexity.

Expecting the generated class to handle deeply nested polymorphic structures correctly

Fix: The tool generates one fixed class per JSON object shape. If a JSON field can contain different object shapes based on a discriminator field — a type field that determines whether the nested object is a TextContent or an ImageContent — the generated class cannot represent this. The initializer will use one fixed nested class constructor regardless of the type. For polymorphic JSON, implement a factory pattern in your initializer: instead of @content = Content.new(hash['content']), use @content = hash['content']['type'] == 'text' ? TextContent.new(hash['content']) : ImageContent.new(hash['content']). The tool gives you the individual class shapes — the dispatch logic requires manual implementation.

Does it support Struct?

The tool generates PORO classes with attr_accessor and initialize. Ruby's built-in Struct is a lighter alternative for simple immutable value objects. If you want a Struct instead of a full class, the structure is straightforward: UserStruct = Struct.new(:id, :name, :email, keyword_init: true). You lose the fromJSON / initialize hash mapping that the generated class provides, but Struct gives you equality comparison, to_a, to_h, and pattern matching support out of the box. Use the generated PORO for API data objects where you want custom methods and explicit initialization logic. Use Struct for simple value objects where the built-in behavior is sufficient.

How does it handle camelCase JSON keys?

JSON from REST APIs commonly uses camelCase field names (isActive, createdAt, userName) while Ruby convention is snake_case (is_active, created_at, user_name). The generated class converts every camelCase JSON key to snake_case for the attr_accessor name and the instance variable, while preserving the original camelCase key in the initialize hash lookup. So @is_active = hash['isActive'] — the left side uses the snake_case Ruby convention and the right side uses the original JSON key. You access the attribute as user.is_active in Ruby code but the underlying JSON parsing still reads from 'isActive'. This is handled automatically for all fields.

Is it safe to use with internal API response data?

Yes. All processing runs locally in your browser. The JSON you paste — including API responses with authentication tokens, internal service URLs, user data, or proprietary response structures — never leaves your machine and is never transmitted anywhere. Generate the class, close the tab, the data is gone.

What is the difference between a PORO, Struct, OpenStruct, and dry-struct?

PORO (Plain Old Ruby Object) is a standard Ruby class with attr_accessor and a manually written initialize — what this tool generates. Maximum flexibility, no dependencies, you control every behavior. Struct is a built-in Ruby class factory that creates value objects with named members, equality comparison, and to_h/to_a — good for simple immutable data with no custom methods. OpenStruct dynamically creates methods from hash keys at instantiation — convenient for prototyping but slow (it creates singleton methods per instance) and deprecated for performance reasons in Ruby 3.x. dry-struct is a gem from the dry-rb ecosystem that provides typed, immutable struct with coercion and validation, similar to TypeScript interfaces with runtime enforcement. For a quick API integration PORO or Struct are sufficient. For domain objects with strict typing requirements, dry-struct is worth the gem dependency.

How do I use the generated class in a Rails application?

For an API-only Rails app that receives JSON request bodies: parse the request body in your controller and pass the hash to the class: user = User.new(JSON.parse(request.body.read)). Or if Rails has already parsed the body as params, use User.new(params.to_unsafe_h) after appropriate permit filtering. For calling an external API with Faraday or HTTParty and wrapping the response: user = User.from_json(response.body). For a model that bridges an external API to your Rails database layer, put the class in app/services/external/ or app/models/api/ depending on your team's conventions. The generated class does not inherit from ApplicationRecord and does not need to — it is a plain Ruby object.

How do I add validations to the generated class?

Two approaches. First, for a plain Ruby class with no Rails dependency: add a validate method that checks the attributes and raises an ArgumentError or returns false: def validate; raise ArgumentError, 'email is required' if email.nil?; end. Call it at the end of initialize. Second, for a class used with Rails, include ActiveModel::Validations: include ActiveModel::Validations and add validates :email, presence: true. This gives you the same validates macro as ActiveRecord models without database backing. Call valid? to check validations and errors to see validation failures. Either approach can be added to the generated class after generation.

Can I use the generated class with Ruby's JSON.generate for round-trip serialization?

The generated class parses JSON in (from_json) but does not include a to_json method for serialization back to JSON. To add round-trip serialization, implement to_json: def to_json(*args); { id: id, user_name: user_name, email: email }.to_json(*args); end. Note that if the API expects camelCase JSON, your to_json must convert back to camelCase. Alternatively, use the as_json method if you are in a Rails context. For a simpler approach, define a to_h method that returns all attributes as a hash and then call .to_json on the hash: def to_h; { id: id, userName: user_name, email: email }; end. This gives you an explicit mapping from Ruby attribute names back to JSON key names.

What about using the json gem's JSON::Ext::Generator::GeneratorMethods?

Adding require 'json' and including JSON::Ext::Generator::GeneratorMethods in your class gives it a to_json method, but it serializes instance variables by default — including the @ prefix in the JSON keys (@name instead of name) which is almost never what you want. Do not rely on the automatic JSON serialization built into the json gem for custom classes. Instead, define to_json explicitly as a method that returns the correct hash structure with the correct key names as described in the question above. Explicit is always better than magic for serialization — a clear to_json method is immediately understandable; automatic instance variable serialization causes surprises.