2011-07-29 32 views
9

Tôi có cấu trúc dữ liệu sử dụng lớp Set từ Thư viện chuẩn của Ruby. Tôi muốn có thể tuần tự hóa cấu trúc dữ liệu của mình thành một chuỗi JSON.Sử dụng phương thức to_json tùy chỉnh trong các đối tượng lồng nhau

Theo mặc định, Set serializes như một mảng:

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

Đó là tốt cho đến khi bạn cố gắng để deserialize nó.

Vì vậy, tôi đã xác định một tùy chỉnh to_json phương pháp:

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 

Những công trình vĩ đại:

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

Cho đến khi tôi đặt Set thành một Hash hoặc một cái gì đó:

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

Bất kỳ ý tưởng tại sao tùy chỉnh của tôi to_json không được gọi khi Set được lồng trong một obj khác ect?

+0

Bạn đang sử dụng phiên bản Ruby nào? Cố gắng này trong 1.8.7 và nó làm việc như bạn muốn. Có thể bạn đang làm điều gì khác ngăn cản hành vi đó. – RocketR

Trả lời

20

Đoạn đầu tiên là dành cho Rails 3.1 (các phiên bản cũ hơn sẽ khá giống nhau); đoạn thứ hai là dành cho JSON không chuẩn Rails. Chuyển đến cuối nếu tl; dr.


Vấn đề của bạn là Rails thực hiện điều này:

[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 

trong active_support/core_ext/object/to_json.rb. Đặc biệt, nó thay đổi phương thức to_json của Hash chỉ thành một cuộc gọi ActiveSupport::JSON.encode.

Sau đó, nhìn vào ActiveSupport::JSON::Encoding::Encoder, chúng tôi thấy điều này:

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 

Vì vậy, tất cả các mã hóa Rails JSON đi qua as_json. Tuy nhiên, bạn không xác định as_json cho Set của riêng mình, bạn chỉ cần thiết lập to_json và bị nhầm lẫn khi Rails bỏ qua thứ gì đó mà nó không sử dụng.

Nếu bạn thiết lập của riêng bạn Set#as_json:

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

sau đó bạn sẽ có được những gì bạn đang sau trong Rails console và Rails nói chung:

> 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]}}}" 

Hãy ghi nhớ rằng as_json được sử dụng để chuẩn bị một đối tượng để tuần tự hóa JSON và sau đó to_json tạo ra chuỗi JSON thực tế. Các phương thức as_json thường trả về các cấu trúc dữ liệu tuần tự đơn giản, chẳng hạn như Hash và Array, và có các tương tự trực tiếp trong JSON; sau đó, khi bạn có thứ gì đó được cấu trúc như JSON, to_json được sử dụng để tuần tự hóa nó thành một chuỗi JSON tuyến tính.


Khi chúng ta nhìn vào các phi Rails thư viện JSON tiêu chuẩn, chúng ta thấy những thứ như thế này:

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

khỉ vá vào các lớp học cơ bản (Symbol, Time, Date, ...).Vì vậy, một lần nữa, to_json thường được triển khai theo điều khoản của as_json. Trong môi trường này, chúng ta cần phải bao gồm các tiêu chuẩn to_json cũng như trên as_json cho Set:

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 

Và chúng tôi bao gồm phương pháp json_create lớp học của bạn cho bộ giải mã. Khi đã tất cả các thiết lập đúng, chúng ta có được những điều như thế này trong 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]}}}" 

Tóm tắt: Nếu bạn đang ở trong Rails, đừng lo lắng về việc làm bất cứ điều gì với to_json, as_json là gì bạn muốn chơi với. Nếu bạn không ở trong Rails, hãy thực hiện hầu hết lôgic của bạn trong as_json (bất kể tài liệu nói gì) và thêm thực hiện tiêu chuẩn to_json (def to_json(*a);as_json.to_json(*a);end).

0

Tìm kiếm giải pháp cho cùng một vấn đề, tôi đã tìm thấy báo cáo lỗi này trên trình theo dõi vấn đề Rails. Bên cạnh đó nó đã được đóng lại, tôi cho rằng nó vẫn xảy ra trên các phiên bản trước đó. Hy vọng nó có thể giúp đỡ.

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

1

Đây là cách tiếp cận của tôi để nhận được to_json phương pháp cho các lớp tùy chỉnh mà có lẽ hầu hết sẽ không chứa to_a phương pháp (nó đã bị xóa khỏi Object thi lớp thời gian gần đây)

Có một chút ma thuật ở đây sử dụng self.included trong mô-đun. Đây là một bài viết rất hay từ năm 2006 về module có cả hai phương pháp thể hiện và lớp học http://blog.jayfields.com/2006/12/ruby-instance-and-class-methods-from.html

Mô-đun được thiết kế để đưa vào bất kỳ lớp nào để cung cấp chức năng liền mạch to_json. Nó chặn phương thức attr_accessor thay vì sử dụng phương thức của riêng nó để yêu cầu thay đổi tối thiểu cho các lớp hiện có.

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 
} 
Các vấn đề liên quan