Convert YAML string to JSON using Ruby - ruby-on-rails

I am able to convert a YAML file to JSON, but I am not able to convert a YAML string to JSON. Is there any other way to convert YAML string to JSON?
Sample Input
---
:name:
:firstname: Guru
:lastname: Shyam
Expected output
{
"name": {
"firstname": "Guru",
"lastname": "Shyam"
}
}

Try Pysch.load
data = "---\n:name:\n :firstname: Guru\n :lastname: Shyam\n"
Psych.load(data)
-> {
:name => {
:firstname => "Guru",
:lastname=> "Shyam"
}
}

YAML.load_file might help you.
Btw, it is an alias for Psych but has a more convenient name, and included in ruby standart library.
[2] pry(main)> .cat data.yml
---
:name:
:firstname: Guru
:lastname: Shyam
[3] pry(main)> require 'yaml'
=> true
[4] pry(main)> puts YAML.load_file('data.yml').to_json
{"name":{"firstname":"Guru","lastname":"Shyam"}}
=> nil

Related

Use Single Quote instead of colons in Ruby/Rails

I'm trying to get this test to pass:
it "returns an object with the correct fields" do
expected_response = {"animal_time": #curr_time.strftime("%F %T"), "animals":{}}
expect(AnimalHaircutSession.latest_for(#animal_haircut)).to eq(expected_response)
end
When running the test I get this error:
expected: {:animal_time=>"2020-02-14 09:48:30", :animals=>{}}
got: {"animal_time"=>"2020-02-14 16:48:30", "animals"=>{}}
Why is it converting my expected response to colons and how can I set my expected response to use single quotes?
In Ruby colons are used to define hashes with symbols for keys . The keys will be cast into symbols.
{ "foo": "bar" } == { :foo => "bar" } # true
{ "foo": "bar" } != { "foo" => "bar" } # true
If you want to define a hash where the keys are not symbols use hash-rockets (=>).
it "returns an object with the correct fields" do
expected_response = {
"animal_time" => #curr_time.strftime("%F %T"),
"animals" => {}
}
expect(AnimalHaircutSession.latest_for(#animal_haircut)).to eq(expected_response)
end
While Ruby will allow you to mix hash-rockets and colons in a hash it's generally not considered good form.

How I get the substring?

how can I get the substring of the string:
[{
"ProductId"=>198,
"AttributesXml"=>"<Attributes><ProductAttribute ID=\"590\"><ProductAttributeValue><Value>1691</Value></ProductAttributeValue></ProductAttribute></Attributes>",
"StockQuantity"=>1,
"AllowOutOfStockOrders"=>false,
"Sku"=>nil,
"ManufacturerPartNumber"=>nil,
"Gtin"=>nil,
"OverriddenPrice"=>nil,
"NotifyAdminForQuantityBelow"=>1, "Id"=>1756
},
I want only the value of "ProductAtrribute ID": 590. Only the number.
I have checked out the ruby docs, but I cannot find a solution.
The tag has rails, if you are in rails then you can leverage nokogiri gem to handle parsing the AttributesXml section:
require 'nokogiri'
array = [{"ProductId"=>198, "AttributesXml"=>"<Attributes><ProductAttribute
ID=\"590\"><ProductAttributeValue><Value>1691</Value>
</ProductAttributeValue></ProductAttribute></Attributes>",
"StockQuantity"=>1, "AllowOutOfStockOrders"=>false,"Sku"=>nil,
"ManufacturerPartNumber"=>nil, "Gtin"=>nil, "OverriddenPrice"=>nil,
"NotifyAdminForQuantityBelow"=>1, "Id"=>1756}]
array.each do |item|
doc = Nokogiri::XML(item['AttributesXml'])
puts doc.xpath("//ProductAttribute").attribute('ID')
end
=> 590
I'd use:
require 'nokogiri'
foo = [
{
"ProductId"=>198,
"AttributesXml"=>"<Attributes><ProductAttribute ID=\"590\"><ProductAttributeValue><Value>1691</Value></ProductAttributeValue></ProductAttribute></Attributes>",
"StockQuantity"=>1,
"AllowOutOfStockOrders"=>false,
"Sku"=>nil,
"ManufacturerPartNumber"=>nil,
"Gtin"=>nil,
"OverriddenPrice"=>nil,
"NotifyAdminForQuantityBelow"=>1, "Id"=>1756
},
]
foo.map { |hash|
Nokogiri::XML(hash['AttributesXml']).at('ProductAttribute')['ID']
}
# => ["590"]
It'll return the ID from all <ProductAttribute> nodes in all the hashes in the array.
You can make use of scan
array = [{"ProductId"=>198, "AttributesXml"=>"<Attributes>
<ProductAttribute
ID=\"590\"><ProductAttributeValue><Value>1691</Value>
</ProductAttributeValue></ProductAttribute></Attributes>",
"StockQuantity"=>1, "AllowOutOfStockOrders"=>false,""Sku"=>nil,
"ManufacturerPartNumber"=>nil, "Gtin"=>nil, "OverriddenPrice"=>nil,
"NotifyAdminForQuantityBelow"=>1, "Id"=>1756},.. ]
and
array.each do |hash|
hash["AttributesXml"].scan(/ProductAttribute\s+ID=\"(\d+)\"/).flatten
#=> ["590"]
# This will return matched values
end

Synchronize two YAML files

Is there any plugin to synchronize two YAML files ? For example file1 has
en:
key1: "Value1"
key2: "Value2"
es:
key1: "EsValue1"
After synchronizing it needs to add key2: "Value2" in the second file without disturbing the order and the key1 in the second file.
I'm not sure if this will keep the order as you want, but if you use Ruby 1.9 it's pretty likely as it's using sorted hashes. You could read contents of the YAML files into hashes using YAML.load_file and then do something like this:
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
es.merge(en, &merger)
and then dump es hash to the YAML file again.
This solution for recursive merging was suggested here: http://www.ruby-forum.com/topic/142809#635081
You don't really need a plugin to do it:
str = <<EOT
en:
key1: "Value1"
key2: "Value2"
es:
key1: "EsValue1"
EOT
require 'yaml'
yaml = YAML::load(str)
(hash['en'].keys - hash['es'].keys).each{ |k| hash['es'][k] = hash['en'][k] }
>> ap hash #=> nil
{
"en" => {
"key1" => "Value1",
"key2" => "Value2"
},
"es" => {
"key1" => "EsValue1",
"key2" => "Value2"
}
}
If you have an arbitrary number of other hashes to process:
(yaml.keys - ['en']).each do |h|
(yaml['en'].keys - yaml[h].keys).each do |k|
yaml[h][k] = yaml['en'][k]
end
end
So, read the YAML file, run the resulting hash through the code, then write the file again.

How do I build this JSON object in ruby?

I need to bring an array of ruby objects in JSON. I will need to find the item in the JSON object by id, so I think it is best that the id is the key of each object. This structure makes the most sense to me:
{
"1": {"attr1": "val1", "attr2": "val2"},
"2": {"attr1": "val1", "attr2": "val2"},
"3": {"attr1": "val1", "attr2": "val2"}
}
That way I can easily call into the json object like console.log(json_obj[id].attr1)
The issue is that I am not quite sure how to build this in ruby. This is as far as I have gotten:
# in ruby
#book_types = []
BookType.all.each do |bt|
#book_types << {bt.id => {:attr => bt.attr}}
end
#book_types = #book_types.to_json
// In JS
var bookTypes = JSON.parse('<%=raw #book_types %>');
2 questions: How can I build this in ruby? Is there a better way to accomplish what I am doing?
Also just a note that I am building this on the Rails framework
Thanks!
Assuming BookType is an ActiveRecord class, you can just do this:
BookType.all(:select => "attr1, attr2").to_json
...where "attr1, attr2" is a list of the attributes you want to include in your JSON.
If you want the ids as keys, you can do this instead:
BookType.all.inject({}) { |hsh, bt|
hsh[bt.id] = { "attr1" => bt.attr1, "attr2" => bt.attr2 }
hsh
}.to_json

what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?

I am wondering what is the best way to convert a json formatted key value pair to ruby hash with symbol as key:
example:
{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==>
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }
Is there a helper method can do this?
using the json gem when parsing the json string you can pass in the symbolize_names option. See here: http://flori.github.com/json/doc/index.html (look under parse)
eg:
>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"}
Leventix, thank you for your answer.
The Marshal.load(Marshal.dump(h)) method probably has the most integrity of the various methods because it preserves the original key types recursively.
This is important in case you have a nested hash with a mix of string and symbol keys and you want to preserve that mix upon decode (for instance, this could happen if your hash contains your own custom objects in addition to highly complex/nested third-party objects whose keys you cannot manipulate/convert for whatever reason, like a project time constraint).
E.g.:
h = {
:youtube => {
:search => 'daffy', # nested symbol key
'history' => ['goofy', 'mickey'] # nested string key
}
}
Method 1: JSON.parse - symbolizes all keys recursively => Does not preserve original mix
JSON.parse( h.to_json, {:symbolize_names => true} )
=> { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } }
Method 2: ActiveSupport::JSON.decode - symbolizes top-level keys only => Does not preserve original mix
ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
=> { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }
Method 3: Marshal.load - preserves original string/symbol mix in the nested keys. PERFECT!
Marshal.load( Marshal.dump(h) )
=> { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }
Unless there is a drawback that I'm unaware of, I'd think Method 3 is the way to go.
Cheers
There isn't anything built in to do the trick, but it's not too hard to write the code to do it using the JSON gem. There is a symbolize_keys method built into Rails if you're using that, but that doesn't symbolize keys recursively like you need.
require 'json'
def json_to_sym_hash(json)
json.gsub!('\'', '"')
parsed = JSON.parse(json)
symbolize_keys(parsed)
end
def symbolize_keys(hash)
hash.inject({}){|new_hash, key_value|
key, value = key_value
value = symbolize_keys(value) if value.is_a?(Hash)
new_hash[key.to_sym] = value
new_hash
}
end
As Leventix said, the JSON gem only handles double quoted strings (which is technically correct - JSON should be formatted with double quotes). This bit of code will clean that up before trying to parse it.
Recursive method:
require 'json'
def JSON.parse(source, opts = {})
r = JSON.parser.new(source, opts).parse
r = keys_to_symbol(r) if opts[:symbolize_names]
return r
end
def keys_to_symbol(h)
new_hash = {}
h.each do |k,v|
if v.class == String || v.class == Fixnum || v.class == Float
new_hash[k.to_sym] = v
elsif v.class == Hash
new_hash[k.to_sym] = keys_to_symbol(v)
elsif v.class == Array
new_hash[k.to_sym] = keys_to_symbol_array(v)
else
raise ArgumentError, "Type not supported: #{v.class}"
end
end
return new_hash
end
def keys_to_symbol_array(array)
new_array = []
array.each do |i|
if i.class == Hash
new_array << keys_to_symbol(i)
elsif i.class == Array
new_array << keys_to_symbol_array(i)
else
new_array << i
end
end
return new_array
end
Of course, there is a json gem, but that handles only double quotes.
Another way to handle this is to use YAML serialization/deserialization, which also preserves the format of the key:
YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml)
=> {:test=>{"test"=>{":test"=>5}}}
Benefit of this approach it seems like a format that is better suited for REST services...
The most convenient way is by using the nice_hash gem: https://github.com/MarioRuiz/nice_hash
require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"
# on my_hash will have the json as a hash
my_hash = my_str.json
# or you can filter and get what you want
vals = my_str.json(:age, :city)
# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
If you think you might need both string and symbol keys:
JSON.parse(json_string).with_indifferent_access

Resources