arcanemachine

arcanemachine

I’ve been working with some pretty large JSON objects, and the inherently unordered sorting caused by parsing a struct as a map makes it hard to find the keys that I am looking for when dealing with these large objects.

To resolve this, I figured out how to convert structs to JSON while preserving a desired sort order (e.g. alphabetical), by converting the structs to keyword lists before encoding them as JSON.

I’m dumping this info here in case it’s helpful for someone else. There may be errors that I am not aware of, but it has worked for me so far. (Please let me know if you find an issue!)

NOTE: The JSON spec (RFC 8259) defines the JSON key-value pairs as being unordered, but knowing this doesn’t make a large JSON object any easier for a person to read. This guide is intended to help the humans who have to read the JSON, not the machines.

Jason (Hex package)

Jason has a helper function to JSON-encode a keyword list. Here’s how to use it to encode a struct:

lib/your_project/some_struct.ex

defmodule YourProject.SomeStruct do
  use Ecto.Schema

  embedded_schema do
    field :hello, :string
    field :world, :string
  end

  defimpl Jason.Encoder do
    @doc "Sort keys alphabetically."
    def encode(struct, opts) do
      struct
      |> Map.from_struct()
      |> Enum.into([])
      |> Enum.sort()
      |> Jason.Encode.keyword(opts)
    end
  end
end

Try it out in IEx:

 iex(1)> %YourProject.SomeStruct{} |> Jason.encode!()
"{\"hello\":null,\"id\":null,\"world\":null}"
 iex(2)> |> Jason.decode!()
%{"hello" => nil, "id" => nil, "world" => nil}

JSON (Native Elixir module in version >=1.18.0)

The newer native JSON Elixir module does not appear to have the same convenience function offered by Jason, so we need to define it ourselves (inspiration taken from JSON.Encoder.Map):

lib/your_project/json_helpers.ex

defmodule YourProject.JSONHelpers do
  def encode_keyword_list!(value, encoder)

  def encode_keyword_list!([], _encoder), do: "{}"

  def encode_keyword_list!([{key, value}], encoder) do
    [?{, key(key, encoder), ?:, encoder.(value, encoder), ?}]
  end

  def encode_keyword_list!([{key, value} | tail], encoder) do
    [?{, key(key, encoder), ?:, encoder.(value, encoder) | next(tail, encoder)]
  end

  defp next([], _encoder), do: ~c"}"

  defp next([{key, value} | tail], encoder) do
    [?,, key(key, encoder), ?:, encoder.(value, encoder) | next(tail, encoder)]
  end

  defp key(key, encoder) when is_atom(key), do: encoder.(Atom.to_string(key), encoder)
  defp key(key, encoder) when is_binary(key), do: encoder.(key, encoder)
  defp key(key, encoder), do: encoder.(String.Chars.to_string(key), encoder)
end

Here’s the same struct from before, but tweaked to work with the native JSON module and our custom helper function:

lib/your_project/some_struct.ex

defmodule YourProject.SomeStruct do
  use Ecto.Schema

  embedded_schema do
    field :hello, :string
    field :world, :string
  end

  defimpl JSON.Encoder do
    @doc "Sort keys alphabetically."
    def encode(value, encoder) do
      value
      |> Map.from_struct()
      |> Enum.into([])
      |> Enum.sort()
      |> YourProject.JSONHelpers.encode_keyword_list!(encoder)
    end
  end
end

Try it out in IEx:

 iex(1)> %YourProject.SomeStruct{} |> JSON.encode!()
"{\"hello\":null,\"id\":null,\"world\":null}"
 iex(2)> |> JSON.decode!()
%{"hello" => nil, "id" => nil, "world" => nil}

Hopefully this is helpful to someone else out there!

Showing Posts 1 to 3

axelson

axelson

Scenic Core Team

The Jason library also has an OrdereObject module that can be used to preserve order: Jason.OrderedObject — jason v1.4.5

arcanemachine

arcanemachine OP

Oh, right! I never figured out how to use that.

Can you provide an example?

lud

lud

Here you go:

iex(1)> Mix.install [:jason]
:ok
iex(2)> map = %{a: 10, b: 3, c: 1, d: 100}
%{c: 1, a: 10, b: 3, d: 100}
iex(3)> map |> Enum.sort_by(&elem(&1,1)) |> Jason.OrderedObject.new() |> Jason.encode!(pretty: true) |> IO.puts()
{
  "c": 1,
  "b": 3,
  "a": 10,
  "d": 100
}
— All posts loaded —

Where Next? Top

Trending in Discussions Top

cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
achempion
I’ve been using Emacs as my main code editor for more than a two years. It’s a custom build version although I’ve tried doom emacs and sp...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews