Tricky JSON layout using a JSON view

phoenix
json

#1

I need a little help on how I can layout my JSON view with the below layout.

def render("cardsHash.json", %{cards: cards}) do
  %{
       "entities": %{
             "cards": %{

              }
        }
    }
end    

The JSON layout should look like:

entities: {
  cards: {
      132: {
        id: 132,
        board_id: 7,        
        created_at: "2018-11-27T03:03:07.649Z",
        updated_at: "2018-11-27T03:03:07.649Z"
      },
      133: {
        id: 133,
        board_id: 7,        
        created_at: "2018-11-27T03:03:07.661Z",
        updated_at: "2018-11-27T03:03:07.661Z"
      }
  }
}

Any help would be appreciated.


#2

Something like this?

def render("cardsHash.json", %{cards: cards}) do
  %{
       "entities": %{
             "cards": Enum.reduce(cards, %{}, fn card, acc -> Map.put(acc, card.id, %{id: card.id ... }) end)
        }
    }
end  

#3

hmm I believe you can’t have an integer as a key in json… so would think that is not gonna fly… but something like

defmodule Myapp.Web.UserView do
  use Myapp.Web, :view

  def render("cardsHash.json", %{cards: cards}) do
    %{entities:
       %{cards: render_many(cards, __MODULE__, "card.json") }
    }
  end

  def render("card.json", %{card: card}) do
    %{ "#{card.id}":
        %{id: card.id, board_id: card.board_id, created_at: card.created_at, updated_at: card.updated_at}
      }
  end
end

#4

Yes true… it cannot be an integer :slight_smile:

render_many(cards, __MODULE__, "card.json")

PS. I think it will render a list, but request is a map?!


#5

Oh yeah - that should be the case… so @salman you probably want to use something like @kokolegorille’s solution - though with string keys…


#6

So I am trying to use that snippet, but I might be calling it incorrectly from inside another view render function.

I’m calling the card using render_one since it is to be called once, even though I am passing it a cards collection.

project_view.ex

def render("project.json", %{project: project, cards: cards}) do
    %{
      success: true,
      errors: [],
      data: %{     
        entities: render_one(cards, CardView, "cardById.json")
      }
    }
  end

card_view.ex

  def render("cardById.json", %{cards: cards}) do
    %{
      "cards": Enum.reduce(cards, %{}, fn card, acc ->
        Map.put(acc, card.id, %{id: card.id })
      end)
    }
  end

Error:

Could not render “cardById.json” for RealtimeWeb.CardView, please define a matching clause for render/2 or define a template at “lib/realtime_web/templates/card”. No templates were compiled for this module. Assigns: %{phx_template_not_found:

What is wrong with the way I am calling the cardById.json render function?


#7

render_one/3 expects cards to be a single entity and will accordingly pass cards via the assigns as %{card: cards}. Your "cardById.json" render function requires that the assigns contain :cards (plural). Use render(CardView, "cardById.json", cards: cards) instead.

Alternatively, using your original function:

def render("cardsHash.json", %{cards: cards}) do
  %{
    "entities": %{
      "cards": Map.new(cards, &{&1.id, card_object(&1)})
    }
  }
end

defp card_object(card) do
  %{id: card.id, created_at: card.inserted_at, ...}
end

https://hexdocs.pm/elixir/Map.html#new/2
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#&/1


#8

Thanks @net that works.