Skip to content

JSON encode supports encoding keyword lists to objects #14431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

madlep
Copy link
Contributor

@madlep madlep commented Apr 12, 2025

Allow keyword lists with atom keys to be encoding with JSON module

Current:

Interactive Elixir (1.18.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> JSON.encode!([a: "foo", b: "bar"])
** (Protocol.UndefinedError) protocol JSON.Encoder not implemented for type Tuple, the protocol must be explicitly implemented.
...

With change:

Interactive Elixir (1.19.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> JSON.encode!([a: "foo", b: "bar"])
"{\"a\":\"foo\",\"b\":\"bar\"}"

@@ -304,6 +304,8 @@ encode_list(List, Encode) when is_list(List) ->

do_encode_list([], _Encode) ->
<<"[]">>;
do_encode_list([{Key, _Value} | _Rest] = List, Encode) when is_atom(Key) ->
encode_key_value_list_checked(List, Encode);
Copy link
Contributor Author

@madlep madlep Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One edge case that could be done either way is encoding an invalid keyword list list such as [k1: "a", k2: "b", "BAD"], which isn't valid syntax in that form, but could still be expressed as [{:k1, "a", {:k2, "b"}, "BAD"], or duplicate keys like [k1: "a", k2: "b", k1: "c"].

This is using encode_key_value_list_checked/2, which will correctly raise an error on bad input: if there is a dup key, or any element that doesn't match {Key, Value} in the list, but will have a performance overhead.

Alternative is to just use encode_key_value_list/2, which is faster, but will allow duplicate keys to be encoded, and will just skip invalid list items.

@josevalim
Copy link
Member

We must not change the elixir_json.erl file, as that was backported from Erlang, and the goal is to fully remove it. However note that Jason does not support keyword lists, nor does :json by default. So we need at least a discussion and some arguments on why this feature should be added.

cc @michalmuskala

@josevalim
Copy link
Member

In any case, thank you for the PR!

@michalmuskala
Copy link
Member

I would be against such a change since, as you said, it's just a heuristic and the list of pairs, might not actually be pairs after all - this can end up with quite confusing errors.
At the same time, why only pairs with atom keys? In the rest of the module we support any key that implements String.Chars protocol, so this seems quite inconsistent.
As a last point, if you really want to do it, json was designed to make it trivial. In particular, in your application you can easily define a wrapper that will provide it:

def encode!(value) do
  JSON.encode!(value, fn
    [{_, _} | _] = kv, encode -> :json.encode_key_value_list(kv, encode);
    other, encode -> JSON.protocol_encode(other, encode)
  end)
end

@sabiwara
Copy link
Contributor

I would be against such a change since, as you said, it's just a heuristic and the list of pairs

Indeed, and there's also the issue of the empty list possibly meaning an empty object, but being translated as a list.

Perhaps a nice way to achieve this without ambiguity in user land could be to define a custom %JSONKeyValue{keyword: kw} struct implementing the JSON.Encode protocol?

@michalmuskala
Copy link
Member

Perhaps a nice way to achieve this without ambiguity in user land could be to define a custom %JSONKeyValue{keyword: kw} struct implementing the JSON.Encode protocol?

That's what we had in Jason with Jason.OrderedObject

@josevalim
Copy link
Member

Ok, given the feedback above, we won't move forward with this. Thanks! :)

@josevalim josevalim closed this Apr 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants