在嵌套对象中使用自定义to_json方法

时间:2011-07-29 22:17:10

标签: ruby json serialization

我有一个使用Ruby标准库中的Set类的数据结构。我希望能够将我的数据结构序列化为JSON字符串。

默认情况下,将序列化设置为数组:

>> s = Set.new [1,2,3]
>> s.to_json
=> "[1,2,3]"

在尝试反序列化之前哪个是好的。

所以我定义了一个自定义to_json方法:

class Set
  def to_json(*a)
    {
      "json_class" => self.class.name,
      "data" => {
        "elements" => self.to_a
      }
    }.to_json(*a)
  end

  def self.json_create(o)
    new o["data"]["elements"]
  end
end

哪个效果很好:

>> s = Set.new [1,2,3]
>> s.to_json
=> "{\"data\":{\"elements\":[1,2,3]},\"json_class\":\"Set\"}"

直到我把Set放入哈希或其他东西:

>> a = { 'set' => s }
>> a.to_json
=> "{\"set\":[1,2,3]}"

知道为什么当Set嵌套在另一个对象中时,我的自定义to_json没有被调用?

3 个答案:

答案 0 :(得分:20)

第一个块用于Rails 3.1(旧版本几乎相同);第二个块用于标准的非Rails JSON。如果tl;博士

,请跳到最后

你的问题是Rails这样做:

[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
  klass.class_eval <<-RUBY, __FILE__, __LINE__
    # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
    def to_json(options = nil)
      ActiveSupport::JSON.encode(self, options)
    end
  RUBY
end
active_support/core_ext/object/to_json.rb中的

。特别是,这会将哈希的to_json方法更改为ActiveSupport::JSON.encode调用。

然后,看ActiveSupport::JSON::Encoding::Encoder,我们看到了这一点:

def encode(value, use_options = true)
  check_for_circular_references(value) do
    jsonified = use_options ? value.as_json(options_for(value)) : value.as_json
    jsonified.encode_json(self)
  end   
end

所以所有Rails JSON编码都经过as_json。但是,你没有为Set定义自己的as_json,你只是设置to_json并在Rails忽略它不使用的东西时感到困惑。

如果您设置了自己的Set#as_json

class Set
    def as_json(options = { })
        {
            "json_class" => self.class.name,
            "data" => { "elements" => self.to_a }
        }
    end
end

然后你会在Rails控制台和Rails中得到你想要的东西:

> require 'set'
> s = Set.new([1,2,3])
> s.to_json
 => "{\"json_class\":\"Set\",\"data\":{\"elements\":[1,2,3]}}"
> h = { :set => s }
> h.to_json
 => "{\"set\":{\"json_class\":\"Set\",\"data\":{\"elements\":[1,2,3]}}}" 

请记住,as_json用于为JSON序列化准备对象,然后to_json生成实际的JSON字符串。 as_json方法通常返回简单的可序列化数据结构,例如Hash和Array,并且在JSON中具有直接类似物;然后,一旦你有一些像JSON这样的结构,to_json用于将它序列化为一个线性JSON字符串。


当我们查看标准的非Rails JSON库时,我们会看到如下内容:

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

猴子修补了基本类(符号,时间,日期......)。因此,to_json通常以as_json的形式实施。在此环境中,我们需要为Set设置标准to_json以及上述as_json

class Set
    def as_json(options = { })
        {
            "json_class" => self.class.name,
            "data" => { "elements" => self.to_a }
        }
    end
    def to_json(*a)
        as_json.to_json(*a)
    end
    def self.json_create(o)
        new o["data"]["elements"]
    end
end

我们为解码器添加了json_create类方法。一旦完成设置,我们就会在irb中找到这样的内容:

>> s = Set.new([1,2,3])
>> s.as_json
=> {"json_class"=>"Set", "data"=>{"elements"=>[1, 2, 3]}}
>> h = { :set => s }
>> h.to_json
=> "{"set":{"json_class":"Set","data":{"elements":[1,2,3]}}}"

执行摘要:如果您使用的是Rails,请不要担心使用to_json执行任何操作,as_json就是您要使用的内容。如果您不在Rails中,请在as_json中实现大部分逻辑(尽管文档中有说明)并添加标准to_json实现(def to_json(*a);as_json.to_json(*a);end)。

答案 1 :(得分:1)

以下是我为自定义类获取to_json方法的方法,该方法很可能不包含to_a方法(最近已将其从Object类实现中删除)

在模块中使用self.included有一点魔力。这是2006年关于module同时具有实例和类方法http://blog.jayfields.com/2006/12/ruby-instance-and-class-methods-from.html

的非常好的文章

该模块旨在包含在任何类中,以提供无缝的to_json功能。它拦截attr_accessor方法而不是使用它自己的方法,以便对现有类进行最小的更改。

module JSONable
  module ClassMethods
    attr_accessor :attributes

    def attr_accessor *attrs
      self.attributes = Array attrs
      super
    end
  end

  def self.included(base)
    base.extend(ClassMethods)
  end

  def as_json options = {}
    serialized = Hash.new
    self.class.attributes.each do |attribute|
      serialized[attribute] = self.public_send attribute
    end
    serialized
  end

  def to_json *a
    as_json.to_json *a
  end
end


class CustomClass
  include JSONable
  attr_accessor :b, :c 

  def initialize b: nil, c: nil
    self.b, self.c = b, c
  end
end

a = CustomClass.new(b: "q", c: 23)
puts JSON.pretty_generate a

{
  "b": "q",
  "c": 23
}

答案 2 :(得分:0)

寻找同一问题的解决方案,我在Rails问题跟踪器上找到了这个错误报告。除了关闭,我想它仍然发生在早期版本上。希望它可以提供帮助。

https://github.com/rails/rails/issues/576