Can someone explain the difference between to_json and JSON.parse() in Ruby?
They're opposite methods. to_json converts the given object to a JSON string, while JSON.parse() parses the given JSON string and converts it to an object:
json_string = {first_name: 'John', last_name: 'Doe'}.to_json
# => "{\"first_name\":\"John\",\"last_name\":\"Doe\"}"
json_object = JSON.parse(json_string)
# => {"first_name"=>"John", "last_name"=>"Doe"}
json_object['first_name']
# => "John"
Related
First, I have a json:
json = "{\"string_1\": \"{{string_1_value}}\", \"number_1\": \"{{number_1_value}}\"}"
And this hash:
hash = {
"{{string_1_value}}" => "test" //string
"{{number_1_value}}" => 2 //integer
}
What I'd like to do is to replace json with this hash and generate below json.
"{\"string_1\": \"test\", \"number_1\": 2}"
When I do this by String#gsub, I got an Error.
hash.map {|k, v| json.gsub!(k, v)}
=> TypeError (no implicit conversion of Integer into String)
I don't want 2 to be string, i.e.)
"{"string_1": "test", "number_1": "2"}"
Do you have any idea?
Thank you in advance.
First, in ruby comments are marked by # not //. And remember about the comma in hash.
gsub is not the fastest way to replace things, it's better to convert json to regular hash and then convert it again to json.
require 'json'
json = "{\"string_1\": \"{{string_1_value}}\", \"number_1\": \"{{number_1_value}}\"}"
hash = {
"{{string_1_value}}" => "test", #string
"{{number_1_value}}" => 2 #integer
}
# First you should parse your json and change it to hash:
parsed_json = JSON.parse(json)
# Then create keys array
keys = parsed_json.keys
# Create new empty hash
new_hash = {}
# And now fill new hash with keys and values
# (take a look at to_s, it converts values to a String)
hash.each.with_index do |(_k, v), i|
new_hash[keys[i]] = v.to_s
end
# Convert to json at the end
new_hash.to_json
# => "{\"string_1\":\"test\",\"number_1\":\"2\"}"
You can use the Regexp,Hash version of String#gsub to just substitute the patterns with the desired values as follows:
require 'json'
json_string = "{\"string_1\": \"{{string_1_value}}\", \"number_1\": \"{{number_1_value}}\"}"
original_hash= {
"{{string_1_value}}" => "test", #string
"{{number_1_value}}" => 2 #integer
}
#Convert JSON to hash and invert the key value pairs
parsed_json = JSON.parse(json_string).invert
#=>{"{{string_1_value}}"=>"string_1", "{{number_1_value}}"=>"number_1"}
# Convert the hash to JSON and substitute the patterns
original_hash.to_json.gsub(/\{\{.+?\}\}/, parsed_json)
#=> "{\"string_1\":\"test\",\"number_1\":2}"
I have the following JSON-String:
jsonString="""{
"struct1": {
"arg1": 218650.27,
"arg2": 90
},
"struct2": {
"arg1": 346.4
}
}"""
I know already how to convert a JSON-String to a struct, but not a multiple struct like the JSON File above.
Is there any Pkg that helps in this case, or I have to split the JSON file?
You can parse JSON to get a dictionary of objects using JSON3:
julia> u = JSON3.read_json_str(jsonString)
JSON3.Object{Base.CodeUnits{UInt8, String}, Vector{UInt64}} with 2 entries:
:struct1 => {…
:struct2 => {…
julia> keys(u)
KeySet for a JSON3.Object{Base.CodeUnits{UInt8, String}, Vector{UInt64}} with 2 entries. Keys:
:struct1
:struct2
Than each element can be read as a separate Dict (here I cast it to Dict for readibility):
julia> Dict(u[:struct2])
Dict{Symbol, Any} with 1 entry:
:arg1 => 346.4
julia> Dict(u[:struct1])
Dict{Symbol, Any} with 2 entries:
:arg1 => 2.1865e5
:arg2 => 90
Now suppose you have a dedicated Julia struct to fill-in with those values such as:
Base.#kwdef struct MyStruct
arg1::Float64 = 0.0
arg2::Int = 0
end
If you now want to store your JSON in such struct you can do:
julia> [MyStruct(;u[key]...) for key in keys(u)]
2-element Vector{MyStruct}:
MyStruct(218650.27, 90)
MyStruct(346.4, 0)
I'm working on some document enhancements and example code snippets for Ruby's JSON class. I'm puzzled by this option to JSON.parse:
create_additions: If set to false, the Parser doesn't create additions even if a matching class and ::create_id was found. This option defaults to false.
Could someone please provide example code for using this?
Consider this:
require 'json'
class Range
def to_json(*a)
{
'json_class' => self.class.name,
'data' => [ first, last, exclude_end? ]
}.to_json(*a)
end
def self.json_create(o)
new(*o['data'])
end
end
foo = 1 .. 2
Generating JSON:
JSON.generate(foo) # => "{\"json_class\":\"Range\",\"data\":[1,2,false]}"
JSON.generate(foo, { create_additions: false }) # => "{\"json_class\":\"Range\",\"data\":[1,2,false]}"
JSON.generate(foo, { create_additions: true }) # => "{\"json_class\":\"Range\",\"data\":[1,2,false]}"
Parsing the generated JSON:
JSON.parse( JSON.generate(foo) ) # => {"json_class"=>"Range", "data"=>[1, 2, false]}
JSON.parse( JSON.generate(foo), { create_additions: false } ) # => {"json_class"=>"Range", "data"=>[1, 2, false]}
JSON.parse( JSON.generate(foo), { create_additions: true } ) # => 1..2
"2.4.3. JSON.parse and JSON.load" demonstrates a potential bug in JSON that affected create_additions. From there it was a simple thing, just some lines testing the result of toggling the state.
Why they had to close the security hole is for you to research as it involves the specification for JSON serialized data and it being a data-exchange standard, and an example in the JSON docs needs to cover that.
The example is right there in the documentation: https://ruby-doc.org/stdlib-2.6.3/libdoc/json/rdoc/JSON.html#module-JSON-label-Extended+rendering+and+loading+of+Ruby+objects.
The main difference in this respect between parse and load is that the former defaults to not create additions, the latter defaults to do it.
Extended rendering and loading of Ruby objects
provides optional additions allowing to serialize and deserialize Ruby
classes without loosing their type.
# without additions
require "json"
json = JSON.generate({range: 1..3, regex: /test/})
# => '{"range":"1..3","regex":"(?-mix:test)"}'
JSON.parse(json)
# => {"range"=>"1..3", "regex"=>"(?-mix:test)"}
# with additions
require "json/add/range"
require "json/add/regexp"
json = JSON.generate({range: 1..3, regex: /test/})
# => '{"range":{"json_class":"Range","a":[1,3,false]},"regex":{"json_class":"Regexp","o":0,"s":"test"}}'
JSON.parse(json)
# => {"range"=>{"json_class"=>"Range", "a"=>[1, 3, false]}, "regex"=>{"json_class"=>"Regexp", "o"=>0, "s"=>"test"}}
JSON.load(json)
# => {"range"=>1..3, "regex"=>/test/}
See #load for details.
I have a python script that pulls all of the EC2 instance ids and tags in all of the AWS accounts I own. I am trying to parse for only one value of one key. Specifically I only want to parse the Value of the Key email from the response, but I am getting the error: list indices must be integers or slices, not str. Below is my code and the json response.
Code:
import boto3
import json
conn = boto3.resource('ec2',
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=session_token)
instances = conn.instances.filter(Filters=[{'Name': 'instance-state-name', 'Values': ['running']}])
for instance in instances:
host_list = instance.id
host_tags = instance.tags
print(host_tags['Key']['email']['Value'])
Sample JSON:
[{
'Key': 'gitlab',
'Value': 'true'
}, {
'Key': 'portfolio',
'Value': 'xxx'
}, {
'Key': 'runner-manager-name',
'Value': 'xxxxxx'
}, ...
]
Error:
list indices must be integers or slices, not str
Your problem is with the lines:
host_tags = instance.tags
print(host_tags['Key']['email']['Value'])
Rewrite it like this:
host_tags = instance.tags
for tag in host_tags:
print('Key: ' + tag['Key'] + ' Value: ' + tag['Value'])
instance.tags is an array of dict. You need to process each item (tag) in the array. Then you need to process the dict extracting its key / value pairs.
I've created a basic client and server that pass a string, which I've changed to JSON instead. But the JSON string is only parsable before it gets sent through TCP. After it's sent, the string version is identical (after a chomp), but on the server side it no longer processes the JSON correctly. Here is some of my code (with other bits trimmed)
Some of the client code
require 'json'
require 'socket'
foo = {'a' => 1, 'b' => 2, 'c' => 3}
puts foo.to_s + "......."
foo.to_json
puts foo['b'] # => outputs the correct '2' answer
client = TCPSocket.open('localhost', 2000)
client.puts json
client.close;
Some of the server code
require 'socket'
require 'json'
server = TCPServer.open(2000)
while true
client = server.accept # Accept client
response = client.gets
print response
response = response.chomp
response.to_json
puts response['b'] # => outputs 'b'
end
The output 'b' should be '2' instead. How do I fix this?
Thanks
In your server you wrote response.to_json. This turns a string to JSON, then throws it away. And I don't like the .chomp, either.
Try
response = client.gets
hash = JSON.parse(response)
Now hash is a Ruby Hash object with your data in it, and hash['b'] should work correctly.
The problem is that .to_json does not parse JSON inside a string and replace itself with the result. It is used to convert the string into a format that is an acceptable JSON value.
require 'json'
string = "abc"
puts string
puts string.to_json
This will output:
abc
"abc"
The method is added to the String class by the JSON generator and it uses it internally to generate the JSON document.
But why does your response['b'] return "b"?
Because Ruby strings have a method called [] that can be used to:
Return a substring: "abc"[0,2] => "ab"
Return a single character from index: "abc"[1] => "b"
Return a substring if the string contains it: "abc"["bc"] => "bc", "abc"["fg"] => nil
Return a regexp match: "abc"[/^a([a-z])c/, 1] => "b"
and possibly some other ways I can't think of right now.
So this happens because your response is a string that has the character "b" in it:
response = "something with a b"
puts response["b"]
# outputs b
puts response["x"]
# outputs a blank line because response does not contain "x".
Instead of .to_json your code has to call JSON.parse or JSON.load:
data = JSON.parse(response)
puts data['b']