Skip to content

Ruby JSON

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于Web服务、API通信和配置文件。Ruby提供了内置的JSON库,使得JSON数据的解析和生成变得简单直观。本章将详细介绍如何在Ruby中处理JSON数据,包括解析、生成、验证和最佳实践。

🎯 JSON基础

什么是JSON

JSON(JavaScript Object Notation)是一种基于文本的开放标准数据交换格式。它具有以下特点:

  • 轻量级: 相比XML更简洁
  • 易读: 人类可读的文本格式
  • 语言无关: 支持多种编程语言
  • 结构化: 支持对象、数组等复杂数据结构

JSON语法

json
{
  "name": "张三",
  "age": 25,
  "isStudent": true,
  "courses": ["数学", "英语", "计算机"],
  "address": {
    "city": "北京",
    "district": "朝阳区"
  },
  "score": null
}

📖 JSON解析

基本解析

ruby
require 'json'

# 解析JSON字符串
json_string = '{"name": "张三", "age": 25, "city": "北京"}'
parsed_data = JSON.parse(json_string)

puts parsed_data['name']  # 张三
puts parsed_data['age']   # 25
puts parsed_data['city']  # 北京

# 解析JSON数组
json_array = '[1, 2, 3, 4, 5]'
numbers = JSON.parse(json_array)
puts numbers.inspect  # [1, 2, 3, 4, 5]

# 解析复杂JSON结构
complex_json = <<~JSON
{
  "users": [
    {
      "id": 1,
      "name": "张三",
      "email": "zhangsan@example.com",
      "profile": {
        "age": 25,
        "city": "北京"
      }
    },
    {
      "id": 2,
      "name": "李四",
      "email": "lisi@example.com",
      "profile": {
        "age": 30,
        "city": "上海"
      }
    }
  ],
  "total": 2
}
JSON

data = JSON.parse(complex_json)
puts "用户总数: #{data['total']}"
puts "第一个用户: #{data['users'][0]['name']}"
puts "第二个用户年龄: #{data['users'][1]['profile']['age']}"

符号化键解析

ruby
require 'json'

# 使用符号作为键
json_string = '{"name": "张三", "age": 25, "city": "北京"}'

# 默认使用字符串键
default_parsed = JSON.parse(json_string)
puts default_parsed.keys  # ["name", "age", "city"]

# 使用符号键
symbolized_parsed = JSON.parse(json_string, symbolize_names: true)
puts symbolized_parsed.keys  # [:name, :age, :city]
puts symbolized_parsed[:name]  # 张三

# 创建自定义对象
class Person
  attr_accessor :name, :age, :city
  
  def initialize(name, age, city)
    @name = name
    @age = age
    @city = city
  end
  
  def self.from_json(json_string)
    data = JSON.parse(json_string, symbolize_names: true)
    new(data[:name], data[:age], data[:city])
  end
end

# 使用自定义对象
person_json = '{"name": "王五", "age": 28, "city": "广州"}'
person = Person.from_json(person_json)
puts "姓名: #{person.name}, 年龄: #{person.age}, 城市: #{person.city}"

错误处理

ruby
require 'json'

# 安全解析JSON
def safe_json_parse(json_string)
  begin
    JSON.parse(json_string)
  rescue JSON::ParserError => e
    puts "JSON解析错误: #{e.message}"
    nil
  end
end

# 有效的JSON
valid_json = '{"name": "张三", "age": 25}'
result = safe_json_parse(valid_json)
puts result.inspect unless result.nil?

# 无效的JSON
invalid_json = '{"name": "张三", "age": 25'  # 缺少结束括号
result = safe_json_parse(invalid_json)
puts "解析结果: #{result}"  # 解析结果: 

# 处理特殊字符
json_with_unicode = '{"message": "你好,世界!"}'
parsed = JSON.parse(json_with_unicode)
puts parsed['message']  # 你好,世界!

🎨 JSON生成

基本生成

ruby
require 'json'

# 从哈希生成JSON
data = {
  name: "张三",
  age: 25,
  city: "北京",
  hobbies: ["读书", "游泳", "编程"]
}

json_string = JSON.generate(data)
puts json_string
# {"name":"张三","age":25,"city":"北京","hobbies":["读书","游泳","编程"]}

# 格式化输出
pretty_json = JSON.pretty_generate(data)
puts pretty_json
# {
#   "name": "张三",
#   "age": 25,
#   "city": "北京",
#   "hobbies": [
#     "读书",
#     "游泳",
#     "编程"
#   ]
# }

# 从数组生成JSON
numbers = [1, 2, 3, 4, 5]
json_array = JSON.generate(numbers)
puts json_array  # [1,2,3,4,5]

自定义对象序列化

ruby
require 'json'

# 为自定义类添加JSON序列化支持
class User
  attr_accessor :id, :name, :email, :created_at
  
  def initialize(id, name, email)
    @id = id
    @name = name
    @email = email
    @created_at = Time.now
  end
  
  # 定义如何转换为JSON
  def to_json(*args)
    {
      id: @id,
      name: @name,
      email: @email,
      created_at: @created_at.iso8601
    }.to_json(*args)
  end
  
  # 从JSON创建对象
  def self.from_json(json_string)
    data = JSON.parse(json_string, symbolize_names: true)
    user = new(data[:id], data[:name], data[:email])
    user.created_at = Time.parse(data[:created_at])
    user
  end
end

# 使用自定义序列化
user = User.new(1, "张三", "zhangsan@example.com")
json_output = user.to_json
puts json_output

# 从JSON恢复对象
restored_user = User.from_json(json_output)
puts "恢复的用户: #{restored_user.name}, 创建时间: #{restored_user.created_at}"

控制JSON输出格式

ruby
require 'json'

# 控制JSON生成选项
data = {
  name: "张三",
  age: 25,
  bio: "这是一个很长的个人简介,包含了很多信息。",
  scores: [95, 87, 92, 88, 90]
}

# 标准生成
puts "=== 标准生成 ==="
puts JSON.generate(data)

# 美化生成
puts "\n=== 美化生成 ==="
puts JSON.pretty_generate(data)

# 自定义缩进
puts "\n=== 自定义缩进 ==="
puts JSON.pretty_generate(data, indent: '  ')

# 限制深度
deep_data = {
  level1: {
    level2: {
      level3: {
        level4: "深层数据"
      }
    }
  }
}

puts "\n=== 限制深度 ==="
puts JSON.pretty_generate(deep_data, max_nesting: 3)

# ASCII编码(避免Unicode转义)
chinese_data = {
  name: "张三",
  city: "北京"
}

puts "\n=== ASCII编码 ==="
puts JSON.generate(chinese_data, ascii_only: true)

puts "\n=== UTF-8编码 ==="
puts JSON.generate(chinese_data)

🔄 JSON与其它数据格式

JSON与YAML转换

ruby
require 'json'
require 'yaml'

# JSON转YAML
json_data = '{"name": "张三", "age": 25, "hobbies": ["读书", "游泳"]}'
parsed_data = JSON.parse(json_data)
yaml_output = YAML.dump(parsed_data)
puts "YAML格式:"
puts yaml_output

# YAML转JSON
yaml_data = <<~YAML
---
name: 李四
age: 30
hobbies:
- 旅游
- 摄影
YAML

yaml_parsed = YAML.load(yaml_data)
json_output = JSON.generate(yaml_parsed)
puts "\nJSON格式:"
puts json_output

JSON与CSV转换

ruby
require 'json'
require 'csv'

# JSON数组转CSV
json_array = <<~JSON
[
  {"name": "张三", "age": 25, "city": "北京"},
  {"name": "李四", "age": 30, "city": "上海"},
  {"name": "王五", "age": 28, "city": "广州"}
]
JSON

data = JSON.parse(json_array)

# 生成CSV
csv_string = CSV.generate do |csv|
  # 写入标题行
  csv << data.first.keys
  
  # 写入数据行
  data.each do |row|
    csv << row.values
  end
end

puts "CSV格式:"
puts csv_string

# CSV转JSON
csv_data = <<~CSV
name,age,city
张三,25,北京
李四,30,上海
王五,28,广州
CSV

csv_parsed = CSV.parse(csv_data, headers: true)
json_output = JSON.pretty_generate(csv_parsed.map(&:to_hash))
puts "\nJSON格式:"
puts json_output

🎯 JSON实用示例

API客户端处理

ruby
require 'json'
require 'net/http'
require 'uri'

class APIClient
  def initialize(base_url)
    @base_url = base_url
  end
  
  def get(path)
    uri = URI.join(@base_url, path)
    response = Net::HTTP.get_response(uri)
    
    if response.code == '200'
      JSON.parse(response.body)
    else
      { error: "HTTP #{response.code}", message: response.message }
    end
  end
  
  def post(path, data)
    uri = URI.join(@base_url, path)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    
    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = 'application/json'
    request.body = JSON.generate(data)
    
    response = http.request(request)
    
    if response.code.start_with?('2')
      JSON.parse(response.body)
    else
      { error: "HTTP #{response.code}", message: response.message }
    end
  end
end

# 使用API客户端
# client = APIClient.new('https://api.example.com/')
# 
# # GET请求
# users = client.get('/api/users')
# puts JSON.pretty_generate(users)
# 
# # POST请求
# new_user = client.post('/api/users', { name: '新用户', email: 'new@example.com' })
# puts JSON.pretty_generate(new_user)

配置文件处理

ruby
require 'json'

class ConfigManager
  def initialize(config_file)
    @config_file = config_file
    @config = load_config
  end
  
  def [](key)
    @config[key]
  end
  
  def []=(key, value)
    @config[key] = value
    save_config
  end
  
  def get(key, default = nil)
    @config.fetch(key, default)
  end
  
  def set(key, value)
    @config[key] = value
    save_config
  end
  
  def reload
    @config = load_config
  end
  
  private
  
  def load_config
    if File.exist?(@config_file)
      JSON.parse(File.read(@config_file))
    else
      {}
    end
  rescue JSON::ParserError => e
    puts "配置文件解析错误: #{e.message}"
    {}
  end
  
  def save_config
    File.write(@config_file, JSON.pretty_generate(@config))
  end
end

# 使用配置管理器
# 创建示例配置文件
sample_config = {
  "database" => {
    "host" => "localhost",
    "port" => 5432,
    "name" => "myapp_db"
  },
  "logging" => {
    "level" => "INFO",
    "file" => "/var/log/myapp.log"
  },
  "features" => {
    "user_management" => true,
    "reporting" => false
  }
}

File.write('app_config.json', JSON.pretty_generate(sample_config))

# 使用配置管理器
config = ConfigManager.new('app_config.json')

puts "数据库主机: #{config['database']['host']}"
puts "日志级别: #{config.get('logging', {})['level']}"
puts "用户管理功能: #{config.get('features', {})['user_management']}"

# 更新配置
config.set('features.user_management', false)
config['logging']['level'] = 'DEBUG'

# 重新加载配置
config.reload
puts "更新后的日志级别: #{config['logging']['level']}"

数据验证和清理

ruby
require 'json'

class JSONValidator
  # 验证JSON结构
  def self.validate_structure(data, schema)
    case schema
    when Hash
      validate_hash(data, schema)
    when Array
      validate_array(data, schema)
    else
      data.is_a?(schema)
    end
  end
  
  # 清理JSON数据
  def self.sanitize(data)
    case data
    when Hash
      data.each_with_object({}) do |(key, value), result|
        result[key.to_s] = sanitize(value)
      end
    when Array
      data.map { |item| sanitize(item) }
    when String
      data.strip
    else
      data
    end
  end
  
  # 深度合并JSON对象
  def self.deep_merge(hash1, hash2)
    merged = hash1.dup
    hash2.each do |key, value|
      merged[key] = if merged[key].is_a?(Hash) && value.is_a?(Hash)
                      deep_merge(merged[key], value)
                    else
                      value
                    end
    end
    merged
  end
  
  private
  
  def self.validate_hash(data, schema)
    return false unless data.is_a?(Hash)
    
    schema.all? do |key, value_schema|
      if key.to_s.end_with?('?')
        # 可选字段
        optional_key = key.to_s.chomp('?').to_sym
        !data.key?(optional_key) || validate_structure(data[optional_key], value_schema)
      else
        # 必需字段
        data.key?(key) && validate_structure(data[key], value_schema)
      end
    end
  end
  
  def self.validate_array(data, schema)
    return false unless data.is_a?(Array)
    return true if schema.empty?
    
    item_schema = schema.first
    data.all? { |item| validate_structure(item, item_schema) }
  end
end

# 使用验证器
user_schema = {
  name: String,
  age: Integer,
  email: String,
  "address?" => {
    street: String,
    city: String
  }
}

valid_user = {
  name: "张三",
  age: 25,
  email: "zhangsan@example.com",
  address: {
    street: "长安街1号",
    city: "北京"
  }
}

invalid_user = {
  name: "李四",
  age: "不是数字",
  email: "lisi@example.com"
}

puts "有效用户验证: #{JSONValidator.validate_structure(valid_user, user_schema)}"
puts "无效用户验证: #{JSONValidator.validate_structure(invalid_user, user_schema)}"

# 使用数据清理
dirty_data = {
  name: "  张三  ",
  age: 25,
  hobbies: [" 读书 ", " 游泳 ", " 编程 "],
  address: {
    city: "  北京  "
  }
}

clean_data = JSONValidator.sanitize(dirty_data)
puts "清理后的数据:"
puts JSON.pretty_generate(clean_data)

# 深度合并
default_config = {
  database: {
    host: "localhost",
    port: 5432,
    pool: 5
  },
  logging: {
    level: "INFO",
    format: "json"
  }
}

user_config = {
  database: {
    host: "production.db.com",
    pool: 10
  },
  features: {
    caching: true
  }
}

merged_config = JSONValidator.deep_merge(default_config, user_config)
puts "合并后的配置:"
puts JSON.pretty_generate(merged_config)

📊 JSON性能优化

大型JSON处理

ruby
require 'json'
require 'stringio'

# 流式JSON解析(适用于大型文件)
class StreamJSONParser
  def self.parse_large_file(file_path)
    File.open(file_path, 'r') do |file|
      # 对于非常大的JSON文件,可以考虑使用yajl-ruby等流式解析库
      # 这里演示基本概念
      json_string = file.read
      JSON.parse(json_string)
    end
  end
  
  # 分块处理JSON数组
  def self.process_json_array_chunks(json_array, chunk_size = 1000)
    json_array.each_slice(chunk_size) do |chunk|
      yield chunk
    end
  end
end

# 创建大型JSON数据进行测试
large_data = {
  users: (1..10000).map do |i|
    {
      id: i,
      name: "用户#{i}",
      email: "user#{i}@example.com",
      score: rand(100)
    }
  end
}

# 写入文件
File.write('large_data.json', JSON.generate(large_data))

# 分块处理
# large_json = JSON.parse(File.read('large_data.json'))
# StreamJSONParser.process_json_array_chunks(large_json['users'], 1000) do |chunk|
#   puts "处理块,大小: #{chunk.length}"
#   # 处理每个块的数据
# end

JSON缓存

ruby
require 'json'
require 'digest'

class JSONCache
  def initialize(cache_dir = './json_cache')
    @cache_dir = cache_dir
    Dir.mkdir(@cache_dir) unless Dir.exist?(@cache_dir)
  end
  
  def fetch(key, expires_in = 3600)
    cache_file = cache_file_path(key)
    
    if File.exist?(cache_file)
      cache_data = JSON.parse(File.read(cache_file))
      if Time.now.to_i < cache_data['expires_at']
        return cache_data['data']
      else
        File.delete(cache_file)
      end
    end
    
    # 缓存未命中,执行块并缓存结果
    data = yield
    cache_data = {
      data: data,
      expires_at: Time.now.to_i + expires_in
    }
    File.write(cache_file, JSON.generate(cache_data))
    
    data
  end
  
  private
  
  def cache_file_path(key)
    hash = Digest::MD5.hexdigest(key)
    File.join(@cache_dir, "#{hash}.json")
  end
end

# 使用JSON缓存
# cache = JSONCache.new
# 
# # 缓存昂贵的计算结果
# result = cache.fetch('expensive_operation') do
#   puts "执行昂贵操作..."
#   sleep(2)  # 模拟耗时操作
#   { message: '操作完成', timestamp: Time.now.to_i }
# end
# 
# puts JSON.pretty_generate(result)

🔍 JSON调试工具

JSON格式化和验证

ruby
require 'json'

class JSONDebugger
  # 格式化JSON字符串
  def self.format_json(json_string, options = {})
    begin
      data = JSON.parse(json_string)
      JSON.pretty_generate(data, options)
    rescue JSON::ParserError => e
      "无效的JSON: #{e.message}"
    end
  end
  
  # 验证JSON字符串
  def self.validate_json(json_string)
    begin
      JSON.parse(json_string)
      { valid: true, error: nil }
    rescue JSON::ParserError => e
      { valid: false, error: e.message }
    end
  end
  
  # 比较两个JSON对象
  def self.compare_json(json1, json2)
    data1 = json1.is_a?(String) ? JSON.parse(json1) : json1
    data2 = json2.is_a?(String) ? JSON.parse(json2) : json2
    
    if data1 == data2
      { equal: true, differences: [] }
    else
      differences = find_differences(data1, data2)
      { equal: false, differences: differences }
    end
  end
  
  private
  
  def self.find_differences(obj1, obj2, path = '')
    differences = []
    
    case [obj1, obj2].map(&:class)
    when [Hash, Hash]
      all_keys = (obj1.keys + obj2.keys).uniq
      all_keys.each do |key|
        current_path = path.empty? ? key.to_s : "#{path}.#{key}"
        if !obj1.key?(key)
          differences << { path: current_path, type: 'missing_in_first', value: obj2[key] }
        elsif !obj2.key?(key)
          differences << { path: current_path, type: 'missing_in_second', value: obj1[key] }
        else
          differences.concat(find_differences(obj1[key], obj2[key], current_path))
        end
      end
    when [Array, Array]
      max_length = [obj1.length, obj2.length].max
      (0...max_length).each do |i|
        current_path = "#{path}[#{i}]"
        if i >= obj1.length
          differences << { path: current_path, type: 'missing_in_first', value: obj2[i] }
        elsif i >= obj2.length
          differences << { path: current_path, type: 'missing_in_second', value: obj1[i] }
        else
          differences.concat(find_differences(obj1[i], obj2[i], current_path))
        end
      end
    else
      if obj1 != obj2
        differences << { path: path, type: 'different_values', first: obj1, second: obj2 }
      end
    end
    
    differences
  end
end

# 使用调试工具
# 格式化JSON
messy_json = '{"name":"张三","age":25,"hobbies":["读书","游泳"],"address":{"city":"北京","district":"朝阳区"}}'
formatted = JSONDebugger.format_json(messy_json)
puts "格式化后的JSON:"
puts formatted

# 验证JSON
valid_json = '{"name": "张三", "age": 25}'
invalid_json = '{"name": "张三", "age": 25'  # 缺少结束括号

puts "\n验证结果:"
puts "有效JSON: #{JSONDebugger.validate_json(valid_json)[:valid]}"
puts "无效JSON: #{JSONDebugger.validate_json(invalid_json)[:valid]}"

# 比较JSON
json1 = '{"name": "张三", "age": 25, "city": "北京"}'
json2 = '{"name": "张三", "age": 26, "city": "上海"}'

comparison = JSONDebugger.compare_json(json1, json2)
puts "\n比较结果:"
puts "是否相等: #{comparison[:equal]}"
puts "差异:"
comparison[:differences].each do |diff|
  puts "  路径: #{diff[:path]}, 类型: #{diff[:type]}"
end

🎯 JSON最佳实践

1. 错误处理

ruby
require 'json'

class SafeJSONHandler
  # 安全解析JSON
  def self.parse(json_string, options = {})
    JSON.parse(json_string, options)
  rescue JSON::ParserError => e
    raise "JSON解析失败: #{e.message} (字符串: #{json_string[0, 100]}...)"
  end
  
  # 安全生成JSON
  def self.generate(data, options = {})
    JSON.generate(data, options)
  rescue JSON::GeneratorError => e
    raise "JSON生成失败: #{e.message}"
  end
  
  # 带默认值的解析
  def self.parse_with_defaults(json_string, defaults = {})
    parsed = parse(json_string, symbolize_names: true)
    defaults.merge(parsed)
  rescue
    defaults
  end
end

# 使用安全处理
begin
  valid_json = '{"name": "张三", "age": 25}'
  data = SafeJSONHandler.parse(valid_json, symbolize_names: true)
  puts "解析成功: #{data}"
  
  invalid_json = '{"name": "张三", "age": 25'
  data = SafeJSONHandler.parse(invalid_json)
rescue => e
  puts "捕获错误: #{e.message}"
end

# 使用默认值
defaults = { name: '未知', age: 0, city: '未知' }
json_with_missing_fields = '{"name": "李四", "age": 30}'
result = SafeJSONHandler.parse_with_defaults(json_with_missing_fields, defaults)
puts "合并结果: #{result}"

2. 数据类型处理

ruby
require 'json'

# 处理特殊数据类型
class JSONTypeHandler
  # 自定义序列化选项
  def self.serialize_with_types(data)
    JSON.generate(data, {
      ascii_only: false,
      max_nesting: 100
    })
  end
  
  # 处理大数字
  def self.handle_large_numbers
    # JavaScript中数字精度限制
    large_number = 9007199254740991  # Number.MAX_SAFE_INTEGER
    
    data = { big_number: large_number }
    json_string = JSON.generate(data)
    parsed = JSON.parse(json_string)
    
    puts "原始数字: #{large_number}"
    puts "JSON字符串: #{json_string}"
    puts "解析后数字: #{parsed['big_number']}"
    puts "是否相等: #{large_number == parsed['big_number']}"
  end
  
  # 处理日期时间
  def self.handle_datetime
    now = Time.now
    data = {
      created_at: now,
      updated_at: now.iso8601
    }
    
    # 默认序列化
    json_default = JSON.generate(data)
    puts "默认序列化: #{json_default}"
    
    # 自定义序列化
    json_custom = JSON.generate(data) do |obj|
      case obj
      when Time, Date, DateTime
        obj.iso8601
      else
        obj
      end
    end
    
    puts "自定义序列化: #{json_custom}"
  end
end

# JSONTypeHandler.handle_large_numbers
# JSONTypeHandler.handle_datetime

3. 安全处理

ruby
require 'json'

class SecureJSONHandler
  # 防止深度嵌套攻击
  def self.safe_parse(json_string, max_depth = 10)
    JSON.parse(json_string, max_nesting: max_depth)
  rescue JSON::NestingError
    raise "JSON嵌套深度超过限制 (最大: #{max_depth})"
  rescue JSON::ParserError => e
    raise "JSON解析错误: #{e.message}"
  end
  
  # 限制JSON大小
  def self.parse_with_size_limit(json_string, max_size = 1024 * 1024)  # 1MB
    if json_string.bytesize > max_size
      raise "JSON大小超过限制 (最大: #{max_size} 字节)"
    end
    
    JSON.parse(json_string)
  end
  
  # 清理潜在危险内容
  def self.sanitize_json(data)
    case data
    when Hash
      data.each_with_object({}) do |(key, value), result|
        # 移除潜在危险的键
        clean_key = key.to_s.gsub(/[<>]/, '')
        result[clean_key] = sanitize_json(value)
      end
    when Array
      data.map { |item| sanitize_json(item) }
    when String
      # 移除潜在的脚本标签
      data.gsub(/<script.*?>.*?<\/script>/mi, '')
    else
      data
    end
  end
end

# 使用安全处理
begin
  # 正常JSON
  normal_json = '{"name": "张三", "age": 25}'
  data = SecureJSONHandler.safe_parse(normal_json)
  puts "正常解析: #{data}"
  
  # 深度嵌套JSON(模拟攻击)
  # nested_json = '{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":"too deep"}}}}}}}}}}}'
  # data = SecureJSONHandler.safe_parse(nested_json)
rescue => e
  puts "安全错误: #{e.message}"
end

📚 下一步学习

掌握了Ruby JSON处理后,建议继续学习:

继续您的Ruby学习之旅吧!

本站内容仅供学习和研究使用。