425

I have a string that I want to parse in Ruby:

string = '{"desc":{"someKey":"someValue","anotherKey":"value"},"main_item":{"stats":{"a":8,"b":12,"c":10}}}'

Is there an easy way to extract the data?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Rod
  • 4,279
  • 2
  • 15
  • 5
  • 27
    _JSON is directly supported in Ruby, and has been since at least Ruby v1.9.3, so there is no need to install a gem unless you're using something older. Simply use `require 'json'` in your code._ – the Tin Man Nov 09 '15 at 17:41

8 Answers8

640

This looks like JavaScript Object Notation (JSON). You can parse JSON that resides in some variable, e.g. json_string, like so:

require 'json'
JSON.parse(json_string)

If you’re using an older Ruby, you may need to install the json gem.


There are also other implementations of JSON for Ruby that may fit some use-cases better:

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
Greg
  • 33,450
  • 15
  • 93
  • 100
  • 43
    Also you can sets the option symbolize_names to true, in order to get keys as symbols. Exemple: `JSON.parse(string, symbolize_names: true) #=> {key: :value}` – Nando Apr 02 '14 at 00:25
  • 33
    _JSON is directly supported in Ruby, and has been since at least Ruby v1.9.3, so there is no need to install a gem unless you're using something older. Simply use `require 'json'` in your code._ – the Tin Man Nov 09 '15 at 17:44
239

Just to extend the answers a bit with what to do with the parsed object:

# JSON Parsing example
require "rubygems" # don't need this if you're Ruby v1.9.3 or higher
require "json"

string = '{"desc":{"someKey":"someValue","anotherKey":"value"},"main_item":{"stats":{"a":8,"b":12,"c":10}}}'
parsed = JSON.parse(string) # returns a hash

p parsed["desc"]["someKey"]
p parsed["main_item"]["stats"]["a"]

# Read JSON from a file, iterate over objects
file = open("shops.json")
json = file.read

parsed = JSON.parse(json)

parsed["shop"].each do |shop|
  p shop["id"]
end
nevan king
  • 112,709
  • 45
  • 203
  • 241
  • 9
    Important note: `'{ "a": "bob" }'` is valid. `"{ 'a': 'bob' }"` is not. – Ziggy Jan 15 '14 at 10:33
  • 7
    @LinusAn because JSON requires double quotes around strings. See string in the JSON definition ( http://www.json.org/ ): "A string is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes." – endorama Apr 11 '14 at 14:46
  • 1
    In many cases you want to wrap `JSON.parse` within a rescue block for `JSON::ParserError`. – johnml Jun 10 '14 at 10:48
  • `JSON.parse("[#{value}]")[0]` to avoid the error `A JSON text must at least contain two octets!` – Rivenfall Mar 16 '16 at 18:14
  • Can you make your answer complete by including a sample example data from shops.json? – barlop May 31 '19 at 12:04
42

As of Ruby v1.9.3 you don't need to install any Gems in order to parse JSON, simply use require 'json':

require 'json'

json = JSON.parse '{"foo":"bar", "ping":"pong"}'
puts json['foo'] # prints "bar"

See JSON at Ruby-Doc.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
dav_i
  • 27,509
  • 17
  • 104
  • 136
18

It looks like a JSON string. You can use one of many JSON libraries and it's as simple as doing:

JSON.parse(string)
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
keymone
  • 8,006
  • 1
  • 28
  • 33
9

Don't see any answers here that mention parsing directly to an object other than a Hash, but it is possible using the poorly-documented object_class option(see https://ruby-doc.org/stdlib-2.7.1/libdoc/json/rdoc/JSON.html):

JSON.parse('{"foo":{"bar": 2}}', object_class: OpenStruct).foo.bar
=> 2

The better way to read that option is "The ruby class that a json object turns into", which explains why it defaults to Hash. Likewise, there is an array_class option for json arrays.

Adverbly
  • 1,726
  • 2
  • 16
  • 23
7

This is a bit late but I ran into something interesting that seems important to contribute.

I accidentally wrote this code, and it seems to work:

require 'yaml'
CONFIG_FILE = ENV['CONFIG_FILE'] # path to a JSON config file 
configs = YAML.load_file("#{CONFIG_FILE}")
puts configs['desc']['someKey']

I was surprised to see it works since I am using the YAML library, but it works.

The reason why it is important is that yaml comes built-in with Ruby so there's no gem install.

I am using versions 1.8.x and 1.9.x - so the json library is not built in, but it is in version 2.x.

So technically - this is the easiest way to extract the data in version lower than 2.0.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
guy mograbi
  • 27,391
  • 16
  • 83
  • 122
  • Yes, JSON is actually parsed by the Psych code, which also parses YAML in Ruby. And JSON parsing was introduced in [Ruby v1.9.3](http://ruby-doc.org/stdlib-1.9.3/libdoc/json/rdoc/index.html). – the Tin Man Nov 09 '15 at 17:42
  • 2
    The reason this works is that semantically (most) JSON is valid YAML (particularly YAML 1.2) – Justin Ohms Jul 18 '16 at 23:05
  • I get "undefined method `load_file' for JSON:Module" on ruby 2.3.8 – Nakilon Dec 16 '22 at 02:47
4

I suggest Oj as it is waaaaaay faster than the standard JSON library.

https://github.com/ohler55/oj

(see performance comparisons here)

mycargus
  • 2,538
  • 3
  • 23
  • 31
damianmr
  • 2,511
  • 1
  • 15
  • 15
1

If you want to deserialise to your own class instead of OpenStruct it doesn't take a lot of work to make the following possible:

require 'json'
# result is an instance of MyClass
result = JSON.parse(some_json_string, object_class: MyClass)

All you have to do is to provide a zero-argument constructor and implement the #[]= method which JSON.parse will call. If you don't want to expose it, it's sufficient to let it be private:

class MyClass
  attr_reader :a, :b

  private

  def []=(key, value)
    case key
    when 'a' then @a = value
    when 'b' then @b = value
    end
  end
end

Trying it out in irb:

> JSON.parse('{"a":1, "b":2}', object_class: MyClass)
=> #<MyClass:0x00007fe00913ae98 @a=1, @b=2>

A caveat with this approach is that it only works for flat structures, because the object_class argument really tells the parser what class it should use to deserialise JSON objects in the string instead of Hash (see the similar argument array_class for the analogous operation for JSON arrays). For nested structures this will mean you will use the same class to represent all layers:

> JSON.parse('{"a":1, "b":{ "a": 32 }}', object_class: MyClass)
=> #<MyClass:0x00007fb5110b2b38 @a=1, @b=#<MyClass:0x00007fb5110b2908 @a=32>>
Erik Madsen
  • 1,913
  • 3
  • 20
  • 34