7

I spent last couple of hours of trying to manually (without Active Record to create a structure like this, taken from an API's documentation:

params_map = '{ "field1" => {"field1a"=>"some text"},
                "field2" => {
                  "field2a"  => "a",
                  "field2b"  => "b",
                  "field2c"  => "c",
                  "field2d"  => {"field2d_a" => "2d_a"},
                  "field2e"  => "e",
                  "items"    => [
                    { "items_a" => "1", 
                      "items_b" => "2",
                      "items_c" => "3",
                      "items_d" => "4"
                    },
                    { "items_e" => "5", 
                      "items_f" => "6",
                      "items_g" => "7",
                      "items_h" => "8"
                    }]
                },
                "field3" => "field3 text",
                "field4" => "field4 text"}'
params_map_eval = eval(params_map)

How can I create this in Ruby? I tried to create it as a Hash using Hash.new, also as a JSON, but I failed at adding/appending another fields to the big JSON.

The items will be a loop and I'll populate it from database, but how do I create this structure in Ruby?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
user984621
  • 46,344
  • 73
  • 224
  • 412
  • 1
    I'm not sure if there is a good way to update JSON. Is it possible to work with it as a hash and then convert it to JSON when you are finished processing? – B Seven Apr 05 '16 at 19:00
  • Yeah that should not be a problem as the final structure will look like above. I tried it through a hash, but I failed at concatenating hash items. – user984621 Apr 05 '16 at 19:01
  • 1
    You can use `Hash#merge` and `#merge!`. I think `#merge!` is faster and uses less memory because it does not need to duplicate the data. See http://ruby-doc.org/core-2.2.0/Hash.html#method-i-merge – B Seven Apr 05 '16 at 19:05
  • Your `params_map` isn't JSON, it's a string version of hash of hashes. Take off the wrapping `'` and you could assign it to `params_map` and get a Ruby hash of hashes. It's difficult to understand your question. – the Tin Man Apr 05 '16 at 19:13
  • I agree with @theTinMan. If you have a JSON string, use JSON#parse to convert it to a Hash, then proceed any way you like. – Todd A. Jacobs Apr 05 '16 at 19:17

5 Answers5

13

If you want to create JSON you use the JSON standard-library which comes with Ruby:

require 'json'

foo = {
  'a' => 1,
  'b' => [2,3]
}

puts JSON[foo]
# >> {"a":1,"b":[2,3]}

If you're trying to figure out how to create a particular JSON output based on a Ruby hash or array, it's easily done by reversing the process and starting with a sample of the desired JSON, and letting the parser turn it into the base object:

JSON['{"a":1,"b":[2,3]}'] # => {"a"=>1, "b"=>[2, 3]}

To modify an existing JSON string, parse it into a Ruby object, modify that, then output it again:

bar = JSON['{"a":1,"b":[2,3]}']
bar['c'] = 'more data'

puts JSON[bar]

# >> {"a":1,"b":[2,3],"c":"more data"}

It's important to note that your example

params_map = '{ "field1" => {"field1a"=>"some text"},
                "field2" => {
                  "field2a"  => "a",
                  "field2b"  => "b",
                  "field2c"  => "c",
                  "field2d"  => {"field2d_a" => "2d_a"},
                  "field2e"  => "e",
                  "items"    => [
                    { "items_a" => "1", 
                      "items_b" => "2",
                      "items_c" => "3",
                      "items_d" => "4"
                    },
                    { "items_e" => "5", 
                      "items_f" => "6",
                      "items_g" => "7",
                      "items_h" => "8"
                    }]
                },
                "field3" => "field3 text",
                "field4" => "field4 text"}'

is not JSON. It's a string:

foo = '{"a" => 1}'
foo.class  # => String

Remove the wrapping ' and you have a hash of hashes:

foo = {"a" => 1, "b" => {"c" => 3}}
foo.class  # => Hash
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
2

Merge or Update Ruby Hashes

Generally, the right thing to do is to use Hash#merge! or Hash#update to modify the contents of a Ruby hash, and then convert the results to JSON. For example:

require 'json'

# Add items to your Hash object.    
params_map = {'field1' => 'foo', 'field2' => 'bar', 'field3' => 'baz'}
params_map.merge! 'field4' => 'quux'
#=> {"field1"=>"foo", "field2"=>"bar", "field3"=>"baz", "field4"=>"quux"}

# Convert Hash object to JSON string.
params_map.to_json
#=> "{\"field1\":\"foo\",\"field2\":\"bar\",\"field3\":\"baz\",\"field4\":\"quux\"}"
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
2

By far the easiest way is to simply create a hash with a similar structure to the JSON, and then simply call .to_json on that hash. You don’t need to do any error-prone/XSS-risky string concatenation to manually create the JSON.

You can use all of the Ruby language to generate your hash, including loops, mapping, variables, merging hashes, string interpolation, etc.

Once the hash has the same structure as the JSON, then just call .to_json on it, and voila, you have valid JSON-encoded version of the hash.

As long as the data types you use in your hashes are valid JSON “primitive”, i.e. nil, strings, numbers, arrays, and other hashes, and all the hash keys are strings (or symbols), or numbers, this should create a 1:1 mapping of Ruby hash to JSON hash.

To generate the above example given in the original post, you could build the hash like so:

params_map = {}
params_map[:field1] = { :field1a => 'some_text' } 
params_map[:field2] = { :field2a =>  'a', :field2b => 'b', :field2c =>  'c' }
params_map[:field2][:field2d] = { :field2d_a => '2d_a' }
params_map[:field2][:field2e] = :e

items = 
[{  :items_a => "1",
    :items_b => "2",
    :items_c => "3",
    :items_d => "4"
}]
items.push({
    :items_e => '5',
    :items_f => '6',
    :items_g => '7'
}.merge('item_h' => "8"))

params_map[:field2][:items] = items
params_map[:field3] = "Field3 text"
params_map.merge!(:field4 => "field4_text")

Notice how I am using different Ruby features to build out the hash.

If you inspect the hash at the end, it will look something like this

>> params_map
=> {:field1=>{:field1a=>"some_text"}, :field2=>{:field2a=>"a", :field2b=>"b", :field2c=>"c", :field2d=>{:field2d_a=>"2d_a"}, :field2e=>:e, :items=>[{:items_a=>"1", :items_b=>"2", :items_c=>"3", :items_d=>"4"}]}, :field3=>"Field3 text"}

You can then simply call params_map.to_json, JSON.dump(params_map), or JSON.pretty_generate(params_map) on the hash to get the JSON representation of the hash, which will in fact be valid JSON.

 >> json = JSON.pretty_generate(params_map) #or use params_map.to_json or JSON.dump(params_map)
 >> puts json
{
  "field1": {
    "field1a": "some_text"
  },
  "field2": {
    "field2a": "a",
    "field2b": "b",
    "field2c": "c",
    "field2d": {
      "field2d_a": "2d_a"
    },
    "field2e": "e",
    "items": [
      {
        "items_a": "1",
        "items_b": "2",
        "items_c": "3",
        "items_d": "4"
      },
      {
        "items_e": "5",
        "items_f": "6",
        "items_g": "7",
        "item_h": "8"
      }
    ]
  },
  "field3": "Field3 text",
  "field4": "field4_text"
}

Then, to make any changes to the JSON, just edit the original params_map hash using Ruby and call .to_json / JSON.dump / JSON.pretty_generate again

arashb31
  • 473
  • 6
  • 12
1

You can try with OpenStruct http://ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html , you can iterate your database and add new key pairs on the fly.

Simple example:

struct = OpenStruct.new
struct.foo = 'bar'
struct.baz = 'biz'
json_struct = struct.to_json

--> {"foo": "bar", "baz": "biz"}
CV-Gate
  • 1,162
  • 1
  • 10
  • 19
-2

Ruby has a library that you can use to work with JSON files. Doing a simple search in StackOverflow I get this old question.

How to write to a JSON file in the correct format

Good luck!

Community
  • 1
  • 1
Juanjo Salvador
  • 1,073
  • 1
  • 12
  • 33