How to render json attributes in order on Phoenix Framework?

Given this view:

def render("index.json", %{resources: resources, pagination: pagination}) do
    %{
      pagination: render_one(pagination, PaginationView, "pagination.json", as: :pagination),
      resources: render_many(resources, HandlerView, "show.json", as: :resource)
    }
  end

  def render("show.json", %{resource: resource}) do
    %{
      id: resource.id, 
      addresses: resource.addresses,
      path: resource.path,
      change: resource.change
    }
  end

I got this result:

{
  "resources": [
    {
      "path": "/user/{id}",
      "id": 1, 
      "change": {
        "kind": "date",
        "field": "updatedAt",
        "dateFormat": "2006-01-02T15:04:05Z07:00"
      },
      "addresses": [
        "http://app.com"
      ]
    }
  ],
  "pagination": {
    "total": 1,
    "limit": 30
  }
}

Pagination is expected to be at the first position. The resource attributes is out of order too. Can this be solved?

2 Likes

No it can’t. Json is not an ordered format.

This is bad. For clients this is not a problem, the parse gonna be the same, but this can generate problems like cache miss for the same request, because each time the body returns different.

Catching should be done via other pragmas and not by body content.

1 Like

While I wholeheartedly agree that the order of JSON attributes shouldn’t be counted on, there are workarounds in cases where something like this is needed.

Since Phoenix uses Poison to encode JSON, one way would be to use a struct and implement the Poison.Encoder protocol for it, then have render return the struct instead of a map:

defmodule Project.OrderedView do
  defstruct pagination: %{}, resources: %{}

  defimpl Poison.Encoder, for: Project.OrderedView do
    def encode(%Project.OrderedView{pagination: p, resources: r}, _options) do
    """
    {
      "pagination": #{Poison.encode!(p)}, 
      "resources": #{Poison.encode!(r)}
    }
    """
    end
  end
...
  def render("index.json", %{resources: resources, pagination: pagination}) do
    %Project.OrderedView{
      pagination: render_one(...),
      resources: render_many(...)
    }
  end
...
end
2 Likes