Ruby objects and JSON serialization (without Rails)
Asked Answered
J

12

75

I'm trying to understand the JSON serialization landscape in Ruby. I'm new to Ruby.

Is there any good JSON serialization options if you are not working with Rails?

That seems to be where this answer goes (to Rails) How to convert a Ruby object to JSON

The json gem seems to make it look like you have to write your own to_json method. I haven't been able to get to_json to work with arrays and hashes (documentation says it works with these) Is there a reason the json gem doesn't just reflect over the object and use a default serialization strategy? Isn't this how to_yaml works (guessing here)

Jeromejeromy answered 16/12, 2010 at 18:19 Comment(1)
Might sound stupid, but did you actually require 'json' ? I worked with the JSON gems and they worked like a charm.Pharaoh
K
116

For the JSON library to be available, you may have to install libjson-ruby from your package manager.

To use the 'json' library:

require 'json'

To convert an object to JSON (these 3 ways are equivalent):

JSON.dump object #returns a JSON string
JSON.generate object #returns a JSON string
object.to_json #returns a JSON string

To convert JSON text to an object (these 2 ways are equivalent):

JSON.load string #returns an object
JSON.parse string #returns an object

It will be a bit more difficult for objects from your own classes. For the following class, to_json will produce something like "\"#<A:0xb76e5728>\"".

class A
    def initialize a=[1,2,3], b='hello'
        @a = a
        @b = b
    end
end

This probably isn't desirable. To effectively serialise your object as JSON, you should create your own to_json method. To go with this, a from_json class method would be useful. You could extend your class like so:

class A
    def to_json
        {'a' => @a, 'b' => @b}.to_json
    end
    def self.from_json string
        data = JSON.load string
        self.new data['a'], data['b']
    end
end

You could automate this by inheriting from a 'JSONable' class:

class JSONable
    def to_json
        hash = {}
        self.instance_variables.each do |var|
            hash[var] = self.instance_variable_get var
        end
        hash.to_json
    end
    def from_json! string
        JSON.load(string).each do |var, val|
            self.instance_variable_set var, val
        end
    end
end

Then you can use object.to_json to serialise to JSON and object.from_json! string to copy the saved state that was saved as the JSON string to the object.

Kure answered 16/12, 2010 at 19:42 Comment(10)
Was looking for a library that didn't require you to write the to_json method on classes. Is there a class you can inherit from that gives you this via reflection? and works on complex objects?Jeromejeromy
I'm looking at the way the YAML code in the stdlib works, but that is little bit much for me to grok at this point.Jeromejeromy
Are you guys sure there is not an easier way ?Croom
To remove the @ from the property name: hash[var.to_s.delete "@"]Thief
hey david4dev, i took your example and added some functionality to support also nested classes. check it out github.com/MarkusPfundstein/JSONableImpede
Very explicit and clear explanation. Helped me, and I'm sure will be useful to others.Aldercy
Warning: JSON.parse and JSON.load are not equivalent. load can be a security risk. Also, parse accepts a String but cannot accept an IO for example.Disencumber
when I create a to_json method on a class it needs one parameter which is of type: JSON::Ext::Generator::State. This appears to be options for formatting the json (ruby-doc.org)Endbrain
This doesn't work: in `to_json': wrong number of arguments (1 for 0) (ArgumentError)Preciado
@Preciado - that a rails thing appearantly. use to_json(options={}) to fix. see #11600110Yaw
I
12

Check out Oj. There are gotchas when it comes to converting any old object to JSON, but Oj can do it.

require 'oj'

class A
    def initialize a=[1,2,3], b='hello'
        @a = a
        @b = b
    end
end

a = A.new
puts Oj::dump a, :indent => 2

This outputs:

{
  "^o":"A",
  "a":[
    1,
    2,
    3
  ],
 "b":"hello"
}

Note that ^o is used to designate the object's class, and is there to aid deserialization. To omit ^o, use :compat mode:

puts Oj::dump a, :indent => 2, :mode => :compat

Output:

{
  "a":[
    1,
    2,
    3
  ],
  "b":"hello"
}
Irruptive answered 19/6, 2013 at 3:10 Comment(3)
Oj has great compatibility for changing classes and deserializing them without additional errors. +1Fishing
How to remove this "^oj" value ?Leghorn
@neustart47: I've edited my answer to show how this can be done, using the :compat mode.Irruptive
S
8

If rendering performance is critical, you might also want to look at yajl-ruby, which is a binding to the C yajl library. The serialization API for that one looks like:

require 'yajl'
Yajl::Encoder.encode({"foo" => "bar"}) #=> "{\"foo\":\"bar\"}"
Subjunctive answered 16/12, 2010 at 19:30 Comment(1)
As I will have to work with a lots of JSON data in my next project, this can be my life saver. +1Rhodic
S
7

What version of Ruby are you using? ruby -v will tell you.

If it's 1.9.2, JSON is included in the standard library.

If you're on 1.8.something then do gem install json and it'll install. Then, in your code do:

require 'rubygems'
require 'json'

Then append to_json to an object and you're good to go:

asdf = {'a' => 'b'} #=> {"a"=>"b"}
asdf.to_json #=> "{"a":"b"}"
Semiliquid answered 16/12, 2010 at 23:58 Comment(3)
Will it be quicker than yajl?Rhodic
That's for you to determine using Benchmark isn't it?Semiliquid
I Thought you maybe know this. Benchmarked and it is slower :P Particularly when encoding.Rhodic
L
7

Since I searched a lot myself to serialize a Ruby Object to json:

require 'json'

class User
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def as_json(options={})
    {
      name: @name,
      age: @age
    }
  end

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

user = User.new("Foo Bar", 42)
puts user.to_json #=> {"name":"Foo Bar","age":42}
Lebna answered 2/8, 2016 at 10:49 Comment(1)
I copied/pasted this into a ruby file, and it executed without a problem. It might be helpful to update your answer to include the output: {"name":"Foo Bar","age":42}Kotick
D
4
require 'json'
{"foo" => "bar"}.to_json
# => "{\"foo\":\"bar\"}"
Dealing answered 16/12, 2010 at 18:22 Comment(0)
H
1

To get the build in classes (like Array and Hash) to support as_json and to_json, you need to require 'json/add/core' (see the readme for details)

Holbrook answered 5/4, 2015 at 13:47 Comment(0)
R
1

Jbuilder is a gem built by rails community. But it works well in non-rails environments and have a cool set of features.

# suppose we have a sample object as below
sampleObj.name #=> foo
sampleObj.last_name #=> bar

# using jbuilder we can convert it to json:
Jbuilder.encode do |json|
  json.name sampleObj.name
  json.last_name sampleObj.last_name
end #=> "{:\"name\" => \"foo\", :\"last_name\" => \"bar\"}"
Rigging answered 25/9, 2016 at 9:49 Comment(0)
P
1

Old thread but still a legitimate question to ask. I have personally been disappointed by serialization options in ruby especially knowing how slow it can be due to gems not considering memory allocations. I have used them all over the years.

I built undraper-serializer and have used this in commercial settings with high volume public APIs with the potential to return 10s of MBs of JSON. The primary advantages of this gem:

  • Very fast
  • Allow selection of fields to return (GraphQL-like selectability)
  • Semi opinionated to control the natural occurrence within large code bases to add and modify so much that the output becomes gluttonous
  • HATEOAS that is easy to do (and optional)
Preempt answered 22/2, 2023 at 3:52 Comment(0)
A
0

If you're using 1.9.2 or above, you can convert hashes and arrays to nested JSON objects just using to_json.

{a: [1,2,3], b: 4}.to_json

In Rails, you can call to_json on Active Record objects. You can pass :include and :only parameters to control the output:

@user.to_json only: [:name, :email]

You can also call to_json on AR relations, like so:

User.order("id DESC").limit(10).to_json

You don't need to import anything and it all works exactly as you'd hope.

Andra answered 10/5, 2013 at 9:37 Comment(0)
P
0

Actually, there is a gem called Jsonable, https://github.com/treeder/jsonable. It's pretty sweet.

Phrasing answered 13/11, 2014 at 11:24 Comment(4)
Jsonable appears defunct: last updated in 2012, and the example code fails -- JSON.parse returns json, not an object.Fishing
Don't know about the examples, but I found Jsonable perfect for my use case: feedjira.herokuapp.com github.com/dentarg/feedjira.herokuapp.com/blob/… github.com/dentarg/feedjira.herokuapp.com/blob/…Phrasing
Maybe it's dependent on Rails or something else. I'm using straight Ruby.Fishing
Me too, not using Rails.Phrasing
U
0

I used to virtus. Really powerful tool, allows to create a dynamic Ruby structure structure based on your specified classes. Easy DSL, possible to create objects from ruby hashes, there is strict mode. Check it out.

Uncertainty answered 15/1, 2016 at 23:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.