michalmuskala / jason

A blazing fast JSON parser and generator in pure Elixir.
Other
1.62k stars 169 forks source link

camelCase Map Key Formatting #48

Closed dennisxtria closed 4 years ago

dennisxtria commented 6 years ago

Hello Michał,

I was wondering if there are any plans to provide an option for selecting key formatting for maps.

Best regards, Dennis

michalmuskala commented 6 years ago

Do you mean when encoding? Right now there's generally no transformation performed on keys when encoding besides converting to strings according to the String.Chars protocol.

If you're asking about decoding, there's already an option where you can provide a function for transforming keys - the primary use-case is to change keys to atoms, but it could also be used to change casing.

dennisxtria commented 6 years ago

Hi! Yes, I meant when encoding, sorry for not making it clear. In a few projects, where I use Jason, there is value in camelCase JSON and the frontend expects it accordingly. Thank you for your response and your time.

selfup commented 6 years ago

Something you could do until this is supported:

This doesn't necessarily solve the problem (there is PR up it seems) but one way to handle this from a JS perspective:

// you can cast vars while destructuring
const {
  foo_bar: fooBar,
  bar_baz: barBaz,
} = data;

const someApiData = {
  fooBar,
  barBaz,
};

// pretend you have a redux store or something
this.props.dispatch({
  type: 'STORE_SOME_API_DATA',
  someApiData,
});

However if jason does convert to camel case, it could be just as easy as:

const someApiData = { ...data };

Just food for thought 🙏

bottlenecked commented 5 years ago

Hi Michal, any chance you're interested in something like this landing in the library? Our use case is that most APIs out there expect camelCase keys. One approach is of course to transform the keys in the map before encoding, but this is more inefficient I guess.

ChezCrawford commented 4 years ago

@michalmuskala: we would be interested in reviving PR #56 if this is something you would consider merging.

Does you like that approach? It seems like it would match well with the existing option in decode mentioned above...

michalmuskala commented 4 years ago

The big issue is that options like this wouldn't be supported in derived struct implementations (since they pre-encode keys at compile-time). Not to mention things like Jason.Fragment that are completely pre-encoded. So it's problematic to support those things in a uniform way that wouldn't be surprising in some circumstances.

michalmuskala commented 4 years ago

One possible way to handle this is similar to the solution I propsoed in #119:

Build a wrapper for maps, where this special handling is required, that hijacks the JSON generation though the protocol:

defmodule Wrapper do
  defstruct [:value]

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

  defimpl Jason.Encoder do
    import Jason.Encode, only: [value: 2, string: 2]

    def encode(%{value: map}, opts) do
      case Map.to_list(map) do
        [] -> 
          "{}"
        [{key, value} | rest] -> 
          ["{", key(key, opts), ":", value(value, opts), encode_loop(rest, opts), "}"]
      end
    end

    defp encode_loop([{key, value} | rest], opts) do
      [",", key(key, opts), ":", value(value, opts) | encode_loop(rest, opts)]
    end

    defp key(other, opts), do: string(camelize(to_string(other)), opts)

    defp camelize(string), do: .... # do actual conversion to camelCase
  end
end

This does a lot of the handling manually, but does avoid double traversal of the data structures and should have comparable performance to native encoders in Jason itself. At this point, it's possible to wrap the maps where you need this special handling with Wrapper.new(map) and pass that to Jason.encode!/2.

Because of the concerns stated above, I don't think we'll have a built-in option for key conversion on encoding in Jason itself. I'm therefore going to close the issue.