Сбор хэшей в OpenStruct создает запись «таблица»

#ruby-on-rails #ruby

#ruby-on-rails #ruby

Вопрос:

Почему это (оценивается в консоли Rails)

 [{:a => :b}].collect {|x| OpenStruct.new(x)}.to_json
  

добавляет туда запись «table»?

 "[{"table":{"a":"b"}}]
  

Я хочу именно этого:

 "[{"a":"b"}]
  

Означает ли это, что метод Rails to_json обрабатывает OpenStruct по-другому? Когда я пробую это в irb, его там нет:

 require 'ostruct'
[{:a => :b}].collect {|x| OpenStruct.new(x)}.inspect
  

Ответ №1:

Потому что @table — это переменная экземпляра OpenStruct и Объект #as_json возвращает хэш переменных экземпляра.

В моем проекте я реализовал OpenStruct#as_json для переопределения поведения.

 require "ostruct"
class OpenStruct
  def as_json(options = nil)
    @table.as_json(options)
  end
end
  

Комментарии:

1. Я OpenStruct весь день пытался обойти метод inspect «s», и это единственное, что сработало для меня. Спасибо.

2. Именно то, что мне нужно! Вы спасли мой день! Спасибо

3. Это вызывает у меня ошибки аргументации. У меня есть Failure и Success классы, которые наследуются от OpenStruct. Чтобы было ясно, я уверен, что это работает с OpenStruct, но с другими моими классами, которые наследуют от него, это ломается. Где я new призываю передавать атрибуты для доступа из моих сервисов. Выдает ошибку, когда я динамически передаю атрибуты / значения новому методу

Ответ №2:

Используйте marshal_dump , хотя это несколько противоречит цели предварительного преобразования его в OpenStruct:

 [{:a => :b}].collect {|x| OpenStruct.new(x).marshal_dump }.to_json
=> "[{"a":"b"}]"
  

Более короткий путь был бы:

 [{:a => :b}].to_json
"[{"a":"b"}]"
  

В качестве альтернативы вы можете использовать денежный патч OpenStruct#as_json , как показано в ответе Хироши:

 require "ostruct"
class OpenStruct
  def as_json(options = nil)
    @table.as_json(options)
  end
end
  

Комментарии:

1. Да, причина, по которой я это делаю, заключается в том, что мне нужно изменить исходную структуру хэша (переименовать некоторые поля, удалить некоторые другие).

2. Работает как шарм. Если есть какой-либо лучший способ сделать это, дайте мне знать: gist.github.com/1300921

3. Есть ли недостаток to_h.as_json(options) , а не ссылки @table ? Кажется более безопасным использовать общедоступный интерфейс, даже если я подклассирую Orstruct .

Ответ №3:

Я решаю проблему, подклассируя OpenStruct следующим образом:

 class DataStruct < OpenStruct
  def as_json(*args)
    super.as_json['table']
  end
end
  

затем вы можете легко преобразовать в JSON следующим образом:

 o = DataStruct.new(a:1, b:DataStruct.new(c:3))
o.to_json
# => "{"a":1,"b":{"c":3}}"
  

Аккуратно, да? Итак, в ответ на ваш вопрос, вы бы написали это вместо:

 [{:a => :b}].collect {|x| DataStruct.new(x)}.to_json
  

предоставляя вам:

 => "[{"a":"b"}]"
  

ОБНОВЛЕНИЕ ДЛЯ RUBY 2.7 (5 февраля 2021)

 require 'json'
require 'ostruct'

class OpenStruct
  def to_json
    to_hash.to_json
  end

  def to_hash
    to_h.map { |k, v|
      v.respond_to?(:to_hash) ? [k, v.to_hash] : [k, v]
    }.to_h
  end
end

o = OpenStruct.new(a:1, b:OpenStruct.new(c:3))
p o.to_json
  

Комментарии:

1. Обратите внимание, что я должен был сделать super['table'] , чтобы заставить это работать.

2. Что именно делает это «для Ruby 2.7»?

Ответ №4:

Я обнаружил, что другие ответы были немного запутанными, приземлившись здесь, чтобы просто выяснить, как превратить мою OpenStruct в a Hash или JSON. Чтобы уточнить, вы можете просто вызвать marshal_dump свой OpenStruct .

 $ OpenStruct.new(hello: :world).to_json
=> "{"table":{"hello":"world"}}"

$ OpenStruct.new(hello: :world).marshal_dump
=> {:hello=>:world}

$ OpenStruct.new(hello: :world).marshal_dump.to_json
=> "{"hello":"world"}"
  

Я лично не решался бы исправлять OpenStruct ошибки, если вы не делаете это в подклассе, поскольку это может иметь непредвиденные последствия.

Комментарии:

1. Это лучший ответ! Мне это нравится

Ответ №5:

С ruby 2.1.2 вы можете использовать следующее, чтобы получить JSON без корневого элемента таблицы:

 [{:a => :b}].collect {|x| OpenStruct.new(x).to_h}.to_json
 => "[{"a":"b"}]"
  

Ответ №6:

 openstruct_array.map(amp;:to_h).as_json
  

Ответ №7:

Проблема здесь в том, что внутренне он выполняет a as_json , который создает хэш с ключом таблицы (потому что as_json также сериализует все переменные экземпляра объекта и @table является переменной экземпляра OpenStruct), а затем выполняет a to_json для того, что его строит.

Итак, самый простой способ — сначала просто использовать to_h (который не сериализует переменные экземпляра), а затем to_json на этом. Итак:

OpenStruct.new(x).to_h.json или в вашем случае open_struct_array.map(amp;:to_h).to_json