Châu Hồng Lĩnh
Châu Hồng Lĩnh

Reputation: 1823

Elixir - JasonHelpers - How can I send a keyword list to json_map?

I have a data structure that I want to convert to json and preserve the key order.

For example: %{ x: 1, a: 5} should be converted to "{\"x\": 1, \"a\": 5}"

Poison does it without any problem. But when I upgrade to Jason, it changes to "{\"a\": 5, \"x\": 1}".

So I use JasonHelpers json_map to preserve the order like this:

Jason.Helpers.json_map([x: 1, a: 5])

It creates a fragment with correct order.

However, when I use a variable to do this:

list = [x: 1, a: 5]
Jason.Helpers.json_map(list)

I have an error:

** (Protocol.UndefinedError) protocol Enumerable not implemented for {:list, [line: 15], nil} of type Tuple.
....

QUESTION: How can I pass a pre-calculated list into Jason.Helpers.json_map ?

The calculation is complicated, so I don't want to repeat the code just to use json_map, but use the function that returns a list.

Upvotes: 1

Views: 1698

Answers (2)

Adam Millerchip
Adam Millerchip

Reputation: 23091

json_map/1 is a macro, from its docs:

Encodes a JSON map from a compile-time keyword.

It is designed for compiling JSON at compile-time, which is why it doesn't work with your runtime variable.

Support for encoding keyword lists was added to the Jason library a year ago, but it looks like it hasn't been pushed to hex yet. I managed to get it work by pulling the latest code from github:

defp deps do
  [{:jason, git: "https://github.com/michalmuskala/jason.git"}]
end

Then by creating a struct that implements Jason.Encoder (adapted from this solution by the Jason author):

defmodule OrderedObject do
  defstruct [:value]

  def new(value), do: %__MODULE__{value: value}

  defimpl Jason.Encoder do
    def encode(%{value: value}, opts) do
      Jason.Encode.keyword(value, opts)
    end
  end
end

Now we can encode objects with ordered keys:

iex(1)> Jason.encode!(OrderedObject.new([x: 1, a: 5]))
"{\"x\":1,\"a\":5}"

Upvotes: 5

Brett Beatty
Brett Beatty

Reputation: 5973

I don't know if this is part of the public API or just an implementation detail, but it appears you have some control of the order when implementing the Jason.Encoder protocol for a struct.

Let's say you've defined an Ordered struct:

defmodule Ordered do
  @derive {Jason.Encoder, only: [:a, :x]}
  defstruct [:a, :x]
end

If you encode the struct, the "a" key will be before the "x" key:

iex> Jason.encode!(%Ordered{a: 5, x: 1})
"{\"a\":5,\"x\":1}"

Let's reorder the keys we pass in to the :only option:

defmodule Ordered do
  @derive {Jason.Encoder, only: [:x, :a]}
  defstruct [:a, :x]
end

If we now encode the struct, the "x" key will be before the "a" key:

iex> Jason.encode!(%Ordered{a: 5, x: 1})
"{\"x\":1,\"a\":5}"

Upvotes: 2

Related Questions