Lectura y escritura de JSON en Ruby

Ruby trae soporte para JSON mediante la librería estándar json (gem incluida con Ruby MRI). Permite serializar (Ruby → JSON) y deserializar (JSON → Ruby) de forma muy directa.

Requisito en cada script:

require "json"

1) Deserializar: JSON → Hash / Array (Ruby)

Ejemplo básico

require "json"

json_str = '{"nombre":"Ana","edad":28,"activo":true,"hobbies":["leer","correr"]}'

data = JSON.parse(json_str)           # => {"nombre"=>"Ana", "edad"=>28, "activo"=>true, "hobbies"=>["leer","correr"]}
puts data["nombre"]                   # "Ana"
puts data["hobbies"].first            # "leer"

# Convertir claves a símbolos
data_sym = JSON.parse(json_str, symbolize_names: true)
# => {nombre: "Ana", edad: 28, activo: true, hobbies: ["leer", "correr"]}
puts data_sym[:nombre]

# Manejo de errores al parsear
begin
  JSON.parse('{nombre:"Ana"}') # inválido (faltan comillas en la clave)
rescue JSON::ParserError => e
  warn "JSON inválido: #{e.message}"
end

2) Serializar: Hash / Array (Ruby) → JSON

to_json y JSON.generate

require "json"

usuario = { nombre: "Luis", edad: 35, activo: false, tags: ["ruby", "json"] }

puts usuario.to_json        # => {"nombre":"Luis","edad":35,"activo":false,"tags":["ruby","json"]}
puts JSON.generate(usuario) # idem

# Salida legible (pretty)
puts JSON.pretty_generate(usuario)
# {
#   "nombre": "Luis",
#   "edad": 35,
#   "activo": false,
#   "tags": [
#     "ruby",
#     "json"
#   ]
# }

# También existen JSON.dump(obj, io = nil) y JSON.load, pero en general
# se prefieren JSON.generate / JSON.parse.

3) Leer y escribir archivos JSON

Escribir a archivo

require "json"

producto = { codigo: 101, descripcion: "Teclado mecánico", precio: 1200.5, disponible: true }

File.write("producto.json", JSON.pretty_generate(producto))

Leer desde archivo

require "json"

contenido = File.read("producto.json")
data = JSON.parse(contenido, symbolize_names: true)
puts data[:descripcion]  # "Teclado mecánico"

4) Listas (arrays de objetos)

require "json"

json_list = <<~JSON
[
  {"codigo":201,"descripcion":"Monitor 24","precio":1500.0},
  {"codigo":202,"descripcion":"Notebook","precio":2500.5}
]
JSON

productos = JSON.parse(json_list, symbolize_names: true)
productos.each { |p| puts "#{p[:codigo]} - #{p[:descripcion]} ($#{p[:precio]})" }

5) Objetos propios (clases) → JSON

Para convertir instancias a JSON, definí as_json y/o to_json.

  • as_json retorna una estructura Ruby serializable (Hash/Array/valores primitivos).
  • to_json llama internamente a as_json (si existe).
require "json"

class Producto
  attr_accessor :codigo, :descripcion, :precio, :disponible, :tags

  def initialize(codigo:, descripcion:, precio:, disponible:, tags: [])
    @codigo = codigo
    @descripcion = descripcion
    @precio = precio
    @disponible = disponible
    @tags = tags
  end

  def as_json(*)
    { codigo: @codigo, descripcion: @descripcion, precio: @precio, disponible: @disponible, tags: @tags }
  end

  def to_json(*opts)
    as_json.to_json(*opts)
  end
end

p1 = Producto.new(codigo: 1, descripcion: "Mouse", precio: 850.75, disponible: true, tags: ["oferta"])
puts JSON.pretty_generate(p1)

# JSON → objeto propio (deserializar)
hash = JSON.parse('{"codigo":1,"descripcion":"Mouse","precio":850.75,"disponible":true,"tags":["oferta"]}', symbolize_names: true)
p_from_json = Producto.new(**hash)   # usa keywords para mapear

# Si el JSON puede traer claves extra, filtralas antes o usá slice en Rails/ActiveSupport.

6) Fechas, horas y números

JSON no tiene tipo fecha. En Ruby se suelen serializar como ISO 8601 (string).

require "json"
require "date"

pedido = {
  id: 10,
  creado_en: Time.now.iso8601,         # "2025-09-09T13:27:00-03:00"
  entrega_en: Date.today.next_day.iso8601 # "2025-09-10"
}
puts JSON.pretty_generate(pedido)

# Para parsear de vuelta:
hash = JSON.parse(pedido.to_json, symbolize_names: true)
creado_time = Time.iso8601(hash[:creado_en])
entrega_date = Date.iso8601(hash[:entrega_en])

7) Streaming / archivos grandes

Para grandes volúmenes, usá JSON::Stream (gem externa) o procesá chunk a chunk. Con estándar puro, podés usar JSON::Parser sobre strings grandes.

NDJSON (JSON Lines) simple

# Escribir
File.open("items.ndjson", "w") do |f|
  1.upto(3) do |i|
    f.puts({ id: i, nombre: "Item #{i}" }.to_json)
  end
end

# Leer línea a línea
File.foreach("items.ndjson") do |line|
  obj = JSON.parse(line, symbolize_names: true)
  puts obj[:nombre]
end

8) HTTP: consumir y enviar JSON (Net::HTTP)

require "json"
require "net/http"
require "uri"

uri = URI("https://example.com/api/productos")

# GET (leer JSON)
resp = Net::HTTP.get_response(uri)
if resp.is_a?(Net::HTTPSuccess)
  data = JSON.parse(resp.body, symbolize_names: true)
  p data
end

# POST (enviar JSON)
nuevo = { codigo: 300, descripcion: "Parlantes", precio: 999.99 }
req = Net::HTTP::Post.new(uri, { "Content-Type" => "application/json" })
req.body = JSON.generate(nuevo)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"
res = http.request(req)
puts res.code

En apps web modernas (Rails/Sinatra), suele usarse Faraday / HTTParty o el cliente de Rails.

9) Consejos y buenas prácticas

  • symbolize_names: true si preferís símbolos; caso contrario, strings.
  • JSON.pretty_generate para logs y depuración, JSON.generate para producción (más compacto).
  • Validá entrada externa y rescatá JSON::ParserError.
  • Serializá fechas como ISO 8601 y definí as_json/to_json en tus clases cuando necesites control fino.
  • Para grandes volúmenes, considerá NDJSON o streaming.
  • Cuidá encoding (UTF-8); Ruby/JSON soporta Unicode de forma nativa.

10) Mini-proyecto integrador (archivo + HTTP)

require "json"
require "net/http"
require "uri"

# 1) Generar y guardar
inventario = [
  { codigo: 1, descripcion: "Teclado", precio: 1200.5, disponible: true },
  { codigo: 2, descripcion: "Mouse",   precio: 850.75,  disponible: false }
]
File.write("inventario.json", JSON.pretty_generate(inventario))

# 2) Leer de archivo
data = JSON.parse(File.read("inventario.json"), symbolize_names: true)
puts "Productos: #{data.size}"

# 3) Enviar a una API
uri = URI("https://httpbin.org/post")
req = Net::HTTP::Post.new(uri, { "Content-Type" => "application/json" })
req.body = JSON.generate(data)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
res = http.request(req)
puts "POST status: #{res.code}"
respuesta = JSON.parse(res.body)
puts "Echo length: #{respuesta.dig('json')&.size}"

Resumen

  • JSON.parseJSON.generate / obj.to_json son tus básicos.
  • Manejá errores con JSON::ParserError.
  • pretty_generate para legibilidad, symbolize_names para símbolos.
  • Serializá fechas como ISO 8601 y definí as_json/to_json en clases propias.
  • Para HTTP, usá Net::HTTP o una librería de cliente, siempre con Content-Type: application/json.