Глубокая конвертация OpenStruct в JSON

У меня есть OpenStruct это вложено со многими другими OpenStructs, Как лучше всего конвертировать их все в JSON?

В идеале:

x = OpenStruct.new
x.y = OpenStruct.new
x.y.z = OpenStruct.new
z = 'hello'

x.to_json
// {y: z: 'hello'}

реальность

{ <OpenStruct= ....> }

6 ответов

Решение

Не существует методов по умолчанию для выполнения такой задачи, потому что встроенный #to_hash возвращает представление Hash, но оно не конвертирует значения

Если значение является OpenStruct возвращается как таковой и не преобразуется в Hash,

Однако это не так сложно решить. Вы можете создать метод, который обходит каждый ключ / значение в OpenStruct экземпляр (например, используя each_pair), рекурсивно спускается во вложенные OpenStruct s, если значение OpenStruct и возвращает Hash только основных типов Ruby.

такие Hash затем можно легко сериализовать, используя .to_json или же JSON.dump(hash),

Это очень быстрый пример

def openstruct_to_hash(object, hash = {})
  object.each_pair do |key, value|
    hash[key] = value.is_a?(OpenStruct) ? openstruct_to_hash(value) : value
  end
  hash
end

openstruct_to_hash(OpenStruct.new(foo: 1, bar: OpenStruct.new(baz: 2)))
# => {:foo=>1, :bar=>{:baz=>2}}

Исправления к вышеуказанному решению для обработки массивов

def open_struct_to_hash(object, hash = {})
  object.each_pair do |key, value|
    hash[key] = case value
                  when OpenStruct then open_struct_to_hash(value)
                  when Array then value.map { |v| open_struct_to_hash(v) }
                  else value
                end
  end
  hash
end

в initializers/open_struct.rb:

      require 'ostruct'

# Because @table is a instance variable of OpenStruct and Object#as_json returns Hash of instance variables.
class OpenStruct
  def as_json(options = nil)
    @table.as_json(options)
  end
end

Применение:

      OpenStruct.new({ a: { b: 123 } }).as_json

# Result
{
    "a" => {
        "b" => 123
    }
}

Редактировать:
похоже, это почти то же самое (обратите внимание, что ключи - это символы, а не строки)

      OpenStruct.new({ a: { b: 123 } }).marshal_dump

# Result
{
    :a => {
        :b => 123
    }
}

Вот еще один подход, измененный на основе ответа lancegatlin. Также добавляем метод в сам класс OpenStruct.

class OpenStruct
  def deep_to_h
    each_pair.map do |key, value|
      [
        key,
        case value
          when OpenStruct then value.deep_to_h
          when Array then value.map {|el| el === OpenStruct ? el.deep_to_h : el}
          else value
        end
      ]
    end.to_h
  end

Та же функция, которая может принимать массивы в качестве входных данных

  def openstruct_to_hash(object, hash = {})
    case object
    when OpenStruct then
      object.each_pair do |key, value|
        hash[key] = openstruct_to_hash(value)
      end
      hash
    when Array then
      object.map { |v| openstruct_to_hash(v) }
    else object
    end
  end

Ничего из вышеперечисленного не помогло мне с копированием и вставкой. Добавляем это решение:

      require 'json'

class OpenStruct
  def deep_to_h
    each_pair.map do |key, value|
      [
        key,
        case value
          when OpenStruct then value.deep_to_h
          when Array then value.map {|el| el.class == OpenStruct ? el.deep_to_h : el}
          else value
        end
      ]
    end.to_h
  end
end

json=<<HERE

{
  "string": "fooval",
  "string_array": [
    "arrayval"
  ],
  "int": 2,
  "hash_array": [
    {
      "string": "barval",
      "string2": "bazval"
    },
    {
      "string": "barval2",
      "string2": "bazval2"
    }
  ]
}
HERE

os = JSON.parse(json, object_class: OpenStruct)
puts JSON.pretty_generate os.to_h
puts JSON.pretty_generate os.deep_to_h
Другие вопросы по тегам