ActiveModel::Serializers::JSON - How to map an array of JSON from a returned string to an collection of Ruby objects - ruby

I have code that works for a single instance but the API I am consuming returns an array of data. I have a class to encapsulate this data:
class Brewery
include ActiveModel::Serializers::JSON
attr_accessor :id, :name
def attributes=(hash)
hash.each { |key, value| send("#{key}=", value) }
end
def attributes
instance_values
end
end
And what the returned data looks like is similar to this
[
{
"id": 2,
"name": "Avondale Brewing Co"
},
{
"id": 44,
"name": "Trim Tab Brewing"
}
]
I can marshal a single JSON hash to the class with code such as this:
brewery = Brewery.new
brewery.from_json(single_brewery)
However this doesn't work with the array. I'm relatively new with Ruby so I'm not quite sure what the function to use is or to at least complete the JSON hashes to an array I can map from_json over.
This works but seems clunky
breweries = JSON.parse(brewery_list).map { |b|
brewery = Brewery.new
brewery.from_json(b.to_json)
}

I am unsure why do you find mapping an array clunky, but you might turn your Brewery to be a factory.
class Brewery
...
def self.many(brewery_list)
JSON.parse(brewery_list).
map(&:to_json).
map(&Brewery.new.method(:from_json)
end
end
And use it like this
breweries = Brewery.many(brewery_list)

Related

Ruby Active model serializer with jsonapi , how to characterise links

I am using active model serializer and jsonapi format
I need to get :
{
"data": {
"id": "1234",
"type": "search",
"relationships": {
"foo": {
"data": [
{
"id": "12",
"type": "foo"
}
],
"links": "/foo/12"
},
}
},
I have tried several configuration for links but it does not display as above
require 'active_model_serializers'
module test
class SearchSerializer < ActiveModel::Serializer
has_one :foo, data: true, links: {self: true, related: true}
type 'search'
end
end
I want to respect the jsonapi format
Is anybody with a good example of active model serializer and json_api showing "links" as shwon on above json?
At the moment only the following is displayed
{"data": {
"id": "1234",
"type": "search",
"relationships": {
"foo": {
"data": [
{
"id": "12",
"type": "foo"
}
]
}
},
Note also that I am trying to do that outside the rails framework.
Thanks
Sorry to answer now, but if it is still of anyone interests...
It is quite simple really. First of all it's important to notice that JSON:API specification tell us that the related link should be their URL extension and it's better to show that path through the Search(for that case specific) path, for example: http://localhost:3000/searches/:search_id/foo.
So our SearchSerializer should be something like:
class SearchSerializer < ActiveModel::Serializer
# The attributes
attributes :id, :whatever, :something, :another_one
has_one :foo do
link(:related) { contact_foo_url(object.id) }
end
end
Note also that at this point you should include the routes and the controller show method, as similiar to the bellow:
For the routes.rb:
Rails.application.routes.draw do
resources :searches do
resource :foo, only: [:show]
# Also a best practice to dispose a 'relationships' path for this kinda example
resource :foo, only: [:show], path: 'relationships/foo'
end
end
And for the FoosController.rb:
class FoosController < ApplicationController
before_action :set_search
# GET /searches/1/foo
def show
render json: #search.foo
end
private
# Use callbacks to share common setup or constraints between actions.
def set_search
#search = Search.find(params[:search_id])
end
end
Going off of FredyK's answer, JSONAPI-SERIALIZER, is a lightweight no-rails serializer for your ruby objects (even though it does have rails integration if desired) which could be a much simpler solution than using Active Record.
Also if you are not using rails, JSONAPI-SERIALIZER pairs really well with the new gem EASY-JSONAPI, which is a middleware, parser, and response validator for JSON:API requests and responses.
After looking what is available in ruby for JSONAPI without rails, I ended using the gem JSONAPI-serializers, It is much easier to set and lighter to load (less dependencies). This fit better with PORO
My serializer becomes
require_relative ./common_serializer
module JsonSerializer
class SearchSerializer < CommonSerializer
attributes :foo, include_links: true
def relationship_related_link(attribute_name)
nil
end
end
end
This gem is much easier to use as the methods which create the json can be changed in the serializer (or in a commonClass)

Parse json to ruby object

In ruby how can i parse a json to an array of objects?
Example: i have 2 classes:
class Person
attr_accessor :name, :address, :email, :address
end
And:
class Address
attr_accessor :street, :city, :state, :person
end
When i make a request i get the following json:
{
"data": [
{
"id": 9111316,
"name": "Mason Lee",
"email": "normanodonnell#biospan.com",
"address": {
"state": "American Samoa",
"street": "Cameron Court",
"city": "Wakulla"
}
},
{
"id": 500019,
"name": "Stella Weeks",
"email": "hansenwhitfield#candecor.com",
"address": {
"state": "Nevada",
"street": "Lake Street",
"city": "Wacissa"
}
}
]
}
This json should be parsed into an array of Person.
For now i'm doing:
#json gem
require 'json'
#...
#parse the json and get the 'data'
parsed_json = JSON.parse json
json_data = parsed_json['data']
objects = Array.new
if json_data.kind_of?(Array)
#add each person
json_data.each { |data|
current_person = Person.new
data.each { |k, v|
current_person.send("#{k}=", v)
}
objects.push(current_person)
}
end
#return the array of Person
objects
I have a lot of objects like the above example and do this parse manually is not desirable. There is an automated way to do this?
By "automated way" i mean something like in java with jackson:
ObjectMapper mapper = new ObjectMapper();
List<Person> myObjects = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
You can initialize the Person with the hash:
json_data = JSON.parse(json)['data']
json_data.map do |data|
Person.new data
end
class Person
attr_accessor :name, :email, :address
def initialize params
params.each { |k,v| klass.public_send("#{k}=",v) }
end
end
If you want to choose the class dynamically, you can use:
json_data.map do |data|
klass = 'Person'
klass.get_const.new data
Why not just make the method yourself? Example:
require 'json'
def parse_json_to_class_array(data,root_node,to_klass)
json_data = JSON.parse(data)[root_node]
if json_data.is_a?(Array)
objects = json_data.map do |item|
klass = to_klass.new
item.each { |k,v| klass.public_send("#{k}=",v) }
klass
end
end
objects ||= []
end
Then for your example you could call it like so
json ="{\"data\":[
{\"id\":9111316,
\"name\":\"Mason Lee\",
\"email\":\"normanodonnell#biospan.com\",
\"address\":{
\"state\":\"American Samoa\",
\"street\":\"Cameron Court\",
\"city\":\"Wakulla\"
}
},
{\"id\":500019,
\"name\":\"Stella Weeks\",
\"email\":\"hansenwhitfield#candecor.com\",
\"address\":{
\"state\":\"Nevada\",
\"street\":\"Lake Street\",
\"city\":\"Wacissa\"
}
}
]
}"
class Person
attr_accessor :id, :name,:email, :address
end
parse_json_to_class_array(json,'data',Person)
#=>[#<Person:0x2ede818 #id=9111316, #name="Mason Lee", #email="normanodonnell#biospan.com", #address={"state"=>"American Samoa", "street"=>"Cameron Court", "city"=>"Wakulla"}>,
#<Person:0x2ede7a0 #id=500019, #name="Stella Weeks", #email="hansenwhitfield#candecor.com", #address={"state"=>"Nevada", "street"=>"Lake Street", "city"=>"Wacissa"}>]
Obviously you can expand this implementation to support single objects as well as overwrite Person#address= to perform the same operation and turn the address Hash into an Address object as well but this was not shown in your example so I did not take it this far in my answer.
A more dynamic example can be found Here

How to add new key/value pair to existing JSON object in Ruby

How could I append a new key/value pair to an existing JSON object in Ruby?
My output is:
{
"2d967df3-ee07-4e40-8f65-7bbff59bbb7e": {
"name": "Book1",
"author": "Author1"
}
}
I want to achieve something like this when I add a new key/value pair:
{
"2d967df3-ee07-4e40-8f65-7bbff59bbb7e": {
"name": "Book1",
"author": "Author1"
},
"c55a3632-9bed-4a41-ae40-c1abfe0f332a": {
"name": "Book2",
"author": "Author2"
}
}
This is my method to write to a JSON file:
def create_book(name, author)
tempHash = {
SecureRandom.uuid => {
"name" => name,
"author" => author
}
}
File.open("./books/book.json","w") do |f|
f.write(JSON.pretty_generate(tempHash))
end
end
To clarify, I need to add a second entry to the original file. I tried using append (<<), and that's where my code fails:
file = File.read("./books/book.json")
data_hash = JSON.parse(file)
newJson = data_hash << tempHash
How could I append a new key/value pair to existing JSON object in Ruby?
If you want to add it to an existing file then you should read the JSON first, extract data from it, then add a new hash to an array.
Maybe something like this will solve your problem:
def create_book(name, author)
tempHash = {
SecureRandom.uuid => {
"name" => name,
"author" => author
}
}
data_from_json = JSON[File.read("./books/book.json")]
data_from_json = [data_from_json] if data_from_json.class != Array
File.open("./books/book.json","w") do |f|
f.write(JSON.pretty_generate(data_from_json << tempHash))
end
end
There are also some other ways like manipulating the JSON as a common string but for safety you should extract the data and then create a new JSON file.
If you need the new key/value pair to be in the same JSON element as the previous data, instead of shoveling (<<) the hashes together, merge them.
Additionally this can allow you to put the new key/value pair in the start of the element or in the end, by flipping which hash you merge first.
So, take Maxim's solution from Apr 14 '15, but modify to merge the two hashes together.
data_from_json = JSON[http://File.read("./books/book.json")]
File.open("./books/book.json","w") do |f|
f.write(JSON.pretty_generate([data_from_json.merge(tempHash)])
end

Creating nested Hash in Ruby

I'm trying to write data from a forum into a JSON file. The hierarchy in the JSON file is supposed to look something this:
thread_id
post_id
...some_items...
Or more specifically:
{
"0101": {
"title": "Hi everybody",
"1001": {...},
"1002": {...}
},
}
The relevant part in my function looks like this:
return {
thread_id.to_i => {
:title => title,
post_id.to_i => {...}
}
}
The result is that each post becomes the child of a new parent thread_id:
{
"0101":{
"title":"Hi everybody",
"1001":{...}
},
"0101":{
"1002":{...}
}
}
What am I doing wrong?
First of all, the JSON schema you're trying to achieve is not quite right in my opinion. See what you think of this:
{
"threads": [
{
"id": 100,
"title": "Lorem ipsum dolor sit amet",
...
"posts": [
{
"id": 1000,
"body": "Lorem ipsum dolor sit amet",
...
},
...
]
},
...
]
}
And the answer to your question depends on how your data is starting out, which we don't know, so I'll answer in terms of what I might expect the data structure to look like. (Note: don't use the constant Thread; it is already a Ruby class used for something totally unrelated.)
class ForumThread
def self.serialize(threads)
{ threads: threads.map(&:serialize) }
end
def serialize
attrs_to_serialize.inject({}) do |hash, attr|
hash[attr] = send(attr)
hash
end
end
def serialized_posts
posts.map &:serialize
end
def attrs_to_serialize
[:id, :title, ..., :serialized_posts]
end
end
class ForumPost
def serialize
attrs_to_serialize.inject({}) do |hash, attr|
hash[attr] = send(attr)
hash
end
end
def attrs_to_serialize
# same sort of thing as above
# ...
end
end
# Given the `threads` variable below holds an array or array-like
# object of ForumThread instances you could do this:
JSON.generate ForumThread.serialize(threads) # => { "threads": [...] }

parse json to object ruby

I looked into different resources and still get confused on how to parse a json format to a custom object, for example
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
and JSON file
{
"Resident": [
{
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}
]
}
what's the correct way to parse the json file into a array of 3 Resident object?
Today i was looking for something that converts json to an object, and this works like a charm:
person = JSON.parse(json_string, object_class: OpenStruct)
This way you could do person.education.school or person[0].education.school if the response is an array
I'm leaving it here because might be useful for someone
The following code is more simple:
require 'json'
data = JSON.parse(json_data)
residents = data['Resident'].map { |rd| Resident.new(rd['phone'], rd['addr']) }
If you're using ActiveModel::Serializers::JSON you can just call from_json(json) and your object will be mapped with those values.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name, :age, :awesome
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
instance_values
end
end
json = {name: 'bob', age: 22, awesome: true}.to_json
person = Person.new
person.from_json(json) # => #<Person:0x007fec5e7a0088 #age=22, #awesome=true, #name="bob">
person.name # => "bob"
person.age # => 22
person.awesome # => true
require 'json'
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
s = '{"Resident":[{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"}]}'
j = JSON.parse(s)
objects = j['Resident'].inject([]) { |o,d| o << Resident.new( d['phone'], d['addr'] ) }
p objects[0].phone
"12345"
We recently released a Ruby library static_struct that solves the issue. Check it out.

Resources