I'm using the PhoenixFramework and the library Poison.
Current I'm working on an REST API. Now I need to encode the model Book in two different ways.
In a list of all books with only base informations (GET /books)
In a detailed view with all informations (GET /book/1)
GET /books
{
"books": [
{
"id": 1,
"name": "Book one"
},
{
"id": 2,
"name": "Book two"
},
// ...
]
}
GET /books/1
{
"book": {
"id": 1,
"name": "Book one",
"description": "This is book one.",
"author": "Max Mustermann",
// ...
}
}
Encoder of Book
defimpl Poison.Encoder, for: MyProject.Book do
#attributes [:id, :name, :description, :author]
def encode(project, _options) do
project
|> Map.take(#attributes)
|> Poison.encode!
end
end
Snippet controller
def index(conn, _params) do
books = Repo.all(Book)
json(conn, %{books: books})
end
How to limit the fields? I search for a option like :only or :exclude.
Has anyone experience with this problem?
Thanks for help!
You can use render_many/4 and render_one/4:
defmodule MyApp.BookView do
def render("index.json", %{books: books}) do
render_many(books, __MODULE__, "book.json")
end
def render("show.json", %{book: book}) do
render_one(book, __MODULE__, "full_book.json")
end
def render("book.json", %{book: book}) do
%{
id: book.id,
name: book.name
}
end
def render("full_book.json", %{book: book}) do
%{
id: book.id,
name: book.name,
description: description,
author: book.author
...
}
end
end
Please note that the name in assigns (2nd argument of render) is determined by the module. See Render many to many relationship JSON in Phoenix Framework for an example of using a different name.
You can then call this from your controller using:
render(conn, "index.json", books: Repo.all(Book))
render(conn, "show.json", book: Repo.get(Book, 1))
Related
I'm having a difficult time figuring out how to pull specific information from a json file.
So far I have this:
# Import json library
import json
# Open json database file
with open('jsondatabase.json', 'r') as f:
data = json.load(f)
# assign variables from json data and convert to usable information
identifier = data['ID']
identifier = str(identifier)
name = data['name']
name = str(name)
# Collect data from user to compare with data in json file
print("Please enter your numerical identifier and name: ")
user_id = input("Numerical identifier: ")
user_name = input("Name: ")
if user_id == identifier and user_name == name:
print("Your inputs matched. Congrats.")
else:
print("Your inputs did not match our data. Please try again.")
And that works great for a simple JSON file like this:
{
"ID": "123",
"name": "Bobby"
}
But ideally I need to create a more complex JSON file and can't find deeper information on how to pull specific information from something like this:
{
"Parent": [
{
"Parent_1": [
{
"Name": "Bobby",
"ID": "123"
}
],
"Parent_2": [
{
"Name": "Linda",
"ID": "321"
}
]
}
]
}
Here is an example that you might be able to pick apart.
You could either:
Make a custom de-jsonify object_hook as shown below and do something with it. There is a good tutorial here.
Just gobble up the whole dictionary that you get without a custom de-jsonify and drill down into it and make a list or set of the results. (not shown)
Example:
import json
from collections import namedtuple
data = '''
{
"Parents":
[
{
"Name": "Bobby",
"ID": "123"
},
{
"Name": "Linda",
"ID": "321"
}
]
}
'''
Parent = namedtuple('Parent', ['name', 'id'])
def dejsonify(json_str: dict):
if json_str.get("Name"):
parent = Parent(json_str.get('Name'), int(json_str.get('ID')))
return parent
return json_str
res = json.loads(data, object_hook=dejsonify)
print(res)
# then we can do whatever... if you need lookups by name/id,
# we could put the result into a dictionary
all_parents = {(p.name, p.id) : p for p in res['Parents']}
lookup_from_input = ('Bobby', 123)
print(f'found match: {all_parents.get(lookup_from_input)}')
Result:
{'Parents': [Parent(name='Bobby', id=123), Parent(name='Linda', id=321)]}
found match: Parent(name='Bobby', id=123)
This seems such a simple task, yet it eludes me...
class ViewAllDogs(webapp2.RequestHandler):
""" Returns an array of json objects representing all dogs. """
def get(self):
query = Dog.query()
results = query.fetch(limit = MAX_DOGS) # 100
aList = []
for match in results:
aList.append({'id': match.id, 'name': match.name,
'owner': match.owner, arrival_date':match.arrival_date})
aList.append({'departure_history':{'departure_date': match.departure_date,
'departed_dog': match.departed_dog}})
self.response.headers['Content-Type'] = 'application/json'
self.response.write(json.dumps(aList))
The above, my best attempt to date, gets me:
[
{
"arrival_date": null,
"id": "a link to self",
"owner": 354773,
"name": "Rover"
},
{
"departure_history": {
"departed_dog": "Jake",
"departure_date": 04/24/2017
}
},
# json array of objects continues...
]
What I'm trying to get is the departure_history nested:
[
{
"id": "a link to self...",
"owner": 354773,
"name": "Rover",
"departure_history": {
"departed_dog": "Jake",
"departure_date": 04/24/2017
},
"arrival_date": 04/25/2017,
},
# json array of objects continues...
]
I've tried a bunch of different combinations, looked at json docs, python27 docs, no joy, and burned about way too many hours with this. I got this far with the many related SO posts on this topic. Thanks in advance.
You can simplify a little:
aList = []
for match in results:
aDog = {'id': match.id,
'name': match.name,
'owner': match.owner,
'arrival_date':match.arrival_date,
'departure_history': {
'departure_date': match.departure_date,
'departed_dog': match.departed_dog}
}
aList.append(aDog)
This seems a bit hackish, but it works. If you know a better way, by all means, let me know. Thanks.
class ViewAllDogs(webapp2.RequestHandler):
""" Returns an array of json objects representing all dogs. """
def get(self):
query = Dog.query()
results = query.fetch(limit = MAX_DOGS) # 100
aList = []
i = 0
for match in results:
aList.append({'id': match.id, 'name': match.name,
'owner': match.owner, arrival_date':match.arrival_date})
aList[i]['departure_history'] = ({'departure_history':{'departure_date': match.departure_date,
'departed_dog': match.departed_dog}})
i += 1
self.response.headers['Content-Type'] = 'application/json'
self.response.write(json.dumps(aList))
I am trying to convert a json file which contain object and array to a JSON file.
Below is the JSON file
{
"localbusiness":{
"name": "toto",
"phone": "+11234567890"
},
"date":"05/02/2016",
"time":"5:00pm",
"count":"4",
"userInfo":{
"name": "John Doe",
"phone": "+10987654321",
"email":"john.doe#unknown.com",
"userId":"user1234333"
}
}
my goal is to save this is a database such as MongoId. I would like to use map to get something like:
localbusiness_name => "toto",
localbusiness_phone => "+11234567890",
date => "05/02/2016",
...
userInfo_name => "John Doe"
...
I have tried map but it's not splitting the array of local business or userInfo
def format_entry
ps = #params.map do | h |
ps.merge!(h)
##logger.info("entry #{h}")
end
##logger.info("formatting the data #{ps}")
ps
end
I do not really how to parse each entry and rebuild the name
It looks like to me you are trying to "flatten" the inner hashes into one big hash. Flatten being incorrect because you want to prepend the hash's key to the sub-hash's key. This will require looping through the hash, and then looping again through each sub hash. This code example will only work if you have 1 layer deep. if you have multiple layers, then I would suggest making two methods, or a recursive method.
#business = { # This is a hash, not a json blob, but you can take json and call JSON.parse(blob) to turn it into a hash.
"localbusiness":{
"name": "toto",
"phone": "+11234567890"
},
"date":"05/02/2016",
"time":"5:00pm",
"count":"4",
"userInfo":{
"name": "John Doe",
"phone": "+10987654321",
"email":"john.doe#unknown.com",
"userId":"user1234333"
}
}
#squashed_business = Hash.new
#business.each do |k, v|
if v.is_a? Hash
v.each do |key, value|
#squashed_business.merge! (k.to_s + "_" + key.to_s) => value
end
else
#squashed_business.merge! k => v
end
end
I noticed that you are getting "unexpected" outcomes when enumerating over a hash #params.each { |h| ... } because it gives you both a key and a value. Instead you want to do #params.each { |key, value| ... } as I did in the above code example.
Rails 4 + Grape Gem + Formatted date and date in JSON response
In my grape response - I am trying to find formatted date in JSON response, but unable to find formatted date.
Model - Item
class Item < ActiveRecord::Base
def self.list_view
select("items.id as id, items.availability_date as date, items.name as name, items.description")
.order("id ASC").as_json( except: [ :id, :created_at, :updated_at ])
end
end
API Call
lib/api/v1/item.rb
module API
module V1
class Items < Grape::API
version 'v1'
format :json
resource :item_data do
get do
Item.list_view
end
end
end
end
end
Request
http://localhost:3000/api/v1/item_data
Response
[
{
"description": "Product One Description",
"date": "2016-03-19",
"name": "Product One"
},
{
"description": "Product 2 Desc",
"date": "2016-03-20",
"name": "Product 2"
}
]
Here I am using date_format gem to show formatted date, but unable to produce the output.
Trying to use -
DateFormat.change_to(date, "ONLY_DATE")
require 'date_format'
Try include it on lib/api/v1/item.rb
I have an Ecto model as such:
defmodule Project.Category do
use Project.Web, :model
schema "categories" do
field :name, :string
field :list_order, :integer
field :parent_id, :integer
belongs_to :menu, Project.Menu
has_many :subcategories, Project.Category, foreign_key: :parent_id
timestamps
end
#required_fields ~w(name list_order)
#optional_fields ~w(menu_id parent_id)
def changeset(model, params \\ :empty) do
model
|> cast(params, #required_fields, #optional_fields)
end
end
As you can see the Category model can reference itself via the subcategories atom.
Here is the view associated with this model:
defmodule Project.CategoryView do
use Project.Web, :view
def render("show.json", %{category: category}) do
json = %{
id: category.id,
name: category.name,
list_order: category.list_order
parent_id: category.parent_id
}
if is_list(category.subcategories) do
children = render_many(category.subcategories, Project.CategoryView, "show.json")
Map.put(json, :subcategories, children)
else
json
end
end
end
I have an if condition on subcategories so that I can play nice with Poison when they are not preloaded.
Finally, here are my 2 controller functions that invoke this view:
defmodule Project.CategoryController do
use Project.Web, :controller
alias Project.Category
def show(conn, %{"id" => id}) do
category = Repo.get!(Category, id)
render conn, "show.json", category: category
end
def showWithChildren(conn, %{"id" => id}) do
category = Repo.get!(Category, id)
|> Repo.preload [:subcategories, subcategories: :subcategories]
render conn, "show.json", category: category
end
end
The show function works fine:
{
"parent_id": null,
"name": "a",
"list_order": 4,
"id": 7
}
However, my showWithChildren function is limited to 2 levels of nesting because of how I use preloading:
{
"subcategories": [
{
"subcategories": [
{
"parent_id": 10,
"name": "d",
"list_order": 4,
"id": 11
}
],
"parent_id": 7,
"name": "c",
"list_order": 4,
"id": 10
},
{
"subcategories": [],
"parent_id": 7,
"name": "b",
"list_order": 9,
"id": 13
}
],
"parent_id": null,
"name": "a",
"list_order": 4,
"id": 7
}
For example, the category item 11 above also has subcategories but I am unable to reach them. Those subcategories can also have subcategories themselves, so the potential depth of the hierarchy is n.
I am aware that I need some recursive magic but since I'm new to both functional programming and Elixir, I cannot wrap my head around it. Any help is greatly appreciated.
You can consider doing the preloading in the view, so it works recursively:
def render("show.json", %{category: category}) do
%{id: category.id,
name: category.name,
list_order: category.list_order
parent_id: category.parent_id}
|> add_subcategories(category)
end
defp add_subcategories(json, %{subcategories: subcategories}) when is_list(subcategories) do
children =
subcategories
|> Repo.preload(:subcategories)
|> render_many(Project.CategoryView, "show.json")
Map.put(json, :subcategories, children)
end
defp add_subcategories(json, _category) do
json
end
Keep in mind this is not ideal for two reasons:
Ideally you don't want to do queries in views (but this is is recursive, so it is easier to piggyback in the view rendering)
You are going to emit multiple queries for the second level of subcategories
There is a book called SQL Antipatterns and, if I am not mistaken, it covers how to write tree structures. Your example is exposed as an antipattern in one of the free chapters. It is an excellent book and they explore solutions for all antipatterns.
PS: you want show_with_children and not showWithChildren.