-
Notifications
You must be signed in to change notification settings - Fork 77
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
Provide camelized option according to v1.1 spec #158
Changes from all commits
f3b0931
c88805c
3f7b71a
f27a91e
4d06f9f
90e42eb
f3eb0d1
28cabf3
1caa739
fd14f81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ defmodule JSONAPI.Utils.String do | |
|
||
alias JSONAPI.Deprecation | ||
|
||
@allowed_transformations [:dasherize, :underscore] | ||
@allowed_transformations [:camelize, :dasherize, :underscore] | ||
|
||
@doc """ | ||
Replace dashes between words in `value` with underscores | ||
|
@@ -17,55 +17,26 @@ defmodule JSONAPI.Utils.String do | |
iex> underscore("top-posts") | ||
"top_posts" | ||
|
||
iex> underscore(:top_posts) | ||
"top_posts" | ||
|
||
iex> underscore("-top-posts") | ||
"-top_posts" | ||
|
||
iex> underscore("-top--posts-") | ||
"-top--posts-" | ||
|
||
iex> underscore(%{"foo-bar" => "baz"}) | ||
%{"foo_bar" => "baz"} | ||
|
||
iex> underscore({"foo-bar", "dollar-sol"}) | ||
{"foo_bar", "dollar-sol"} | ||
|
||
iex> underscore({"foo-bar", %{"a-d" => "z-8"}}) | ||
{"foo_bar", %{"a_d" => "z-8"}} | ||
|
||
iex> underscore(%{"f-b" => %{"a-d" => "z"}, "c-d" => "e"}) | ||
%{"f_b" => %{"a_d" => "z"}, "c_d" => "e"} | ||
|
||
iex> underscore(:"foo-bar") | ||
:foo_bar | ||
|
||
iex> underscore(%{"f-b" => "a-d"}) | ||
%{"f_b" => "a-d"} | ||
""" | ||
@spec underscore(String.t()) :: String.t() | ||
def underscore(value) when is_binary(value) do | ||
String.replace(value, ~r/([a-zA-Z0-9])-([a-zA-Z0-9])/, "\\1_\\2") | ||
end | ||
|
||
def underscore(map) when is_map(map) do | ||
Enum.into(map, %{}, &underscore/1) | ||
end | ||
|
||
def underscore({key, value}) when is_map(value) do | ||
{underscore(key), underscore(value)} | ||
end | ||
|
||
def underscore({key, value}) do | ||
{underscore(key), value} | ||
end | ||
|
||
@spec underscore(atom) :: String.t() | ||
def underscore(value) when is_atom(value) do | ||
value | ||
|> to_string() | ||
|> underscore() | ||
|> String.to_atom() | ||
end | ||
|
||
def underscore(value) do | ||
value | ||
end | ||
|
||
@doc """ | ||
|
@@ -83,31 +54,129 @@ defmodule JSONAPI.Utils.String do | |
|
||
iex> dasherize("_top__posts_") | ||
"_top__posts_" | ||
|
||
""" | ||
@spec dasherize(atom) :: String.t() | ||
def dasherize(value) when is_atom(value) do | ||
value | ||
|> to_string() | ||
|> dasherize() | ||
end | ||
|
||
@spec dasherize(String.t()) :: String.t() | ||
def dasherize(value) when is_binary(value) do | ||
String.replace(value, ~r/([a-zA-Z0-9])_([a-zA-Z0-9])/, "\\1-\\2") | ||
end | ||
|
||
def dasherize(%{__struct__: _} = value) when is_map(value) do | ||
@doc """ | ||
Replace underscores or dashes between words in `value` with camelCasing | ||
|
||
Ignores underscores or dashes that are not between letters/numbers | ||
|
||
## Examples | ||
|
||
iex> camelize("top_posts") | ||
"topPosts" | ||
|
||
iex> camelize(:top_posts) | ||
"topPosts" | ||
|
||
iex> camelize("_top_posts") | ||
"_topPosts" | ||
|
||
iex> camelize("_top__posts_") | ||
"_top__posts_" | ||
|
||
""" | ||
@spec camelize(atom) :: String.t() | ||
def camelize(value) when is_atom(value) do | ||
value | ||
|> to_string() | ||
|> camelize() | ||
end | ||
|
||
def dasherize(value) when is_map(value) do | ||
Enum.into(value, %{}, &dasherize/1) | ||
@spec camelize(String.t()) :: String.t() | ||
def camelize(value) when is_binary(value) do | ||
with words <- | ||
Regex.split( | ||
~r{(?<=[a-zA-Z0-9])[-_](?=[a-zA-Z0-9])}, | ||
to_string(value) | ||
) do | ||
[h | t] = words |> Enum.filter(&(&1 != "")) | ||
|
||
[String.downcase(h) | camelize_list(t)] | ||
|> Enum.join() | ||
end | ||
end | ||
|
||
def dasherize({key, value}) do | ||
if is_map(value) do | ||
{dasherize(key), dasherize(value)} | ||
else | ||
{dasherize(key), value} | ||
end | ||
defp camelize_list([]), do: [] | ||
|
||
defp camelize_list([h | t]) do | ||
[String.capitalize(h)] ++ camelize_list(t) | ||
end | ||
|
||
@doc """ | ||
|
||
## Examples | ||
|
||
iex> expand_fields(%{"foo-bar" => "baz"}, &underscore/1) | ||
%{"foo_bar" => "baz"} | ||
|
||
iex> expand_fields(%{"foo_bar" => "baz"}, &dasherize/1) | ||
%{"foo-bar" => "baz"} | ||
|
||
iex> expand_fields(%{"foo-bar" => "baz"}, &camelize/1) | ||
%{"fooBar" => "baz"} | ||
|
||
iex> expand_fields({"foo-bar", "dollar-sol"}, &underscore/1) | ||
{"foo_bar", "dollar-sol"} | ||
|
||
iex> expand_fields({"foo-bar", %{"a-d" => "z-8"}}, &underscore/1) | ||
{"foo_bar", %{"a_d" => "z-8"}} | ||
|
||
iex> expand_fields(%{"f-b" => %{"a-d" => "z"}, "c-d" => "e"}, &underscore/1) | ||
%{"f_b" => %{"a_d" => "z"}, "c_d" => "e"} | ||
|
||
iex> expand_fields(%{"f-b" => %{"a-d" => %{"z-w" => "z"}}, "c-d" => "e"}, &underscore/1) | ||
%{"f_b" => %{"a_d" => %{"z_w" => "z"}}, "c_d" => "e"} | ||
|
||
iex> expand_fields(:"foo-bar", &underscore/1) | ||
"foo_bar" | ||
|
||
iex> expand_fields(:foo_bar, &dasherize/1) | ||
"foo-bar" | ||
|
||
iex> expand_fields(:"foo-bar", &camelize/1) | ||
"fooBar" | ||
|
||
iex> expand_fields(%{"f-b" => "a-d"}, &underscore/1) | ||
%{"f_b" => "a-d"} | ||
|
||
iex> expand_fields(%{"inserted-at" => ~N[2019-01-17 03:27:24.776957]}, &underscore/1) | ||
%{"inserted_at" => ~N[2019-01-17 03:27:24.776957]} | ||
|
||
""" | ||
@spec expand_fields(map, function) :: map | ||
def expand_fields(%{__struct__: _} = value, _fun), do: value | ||
|
||
@spec expand_fields(map, function) :: map | ||
def expand_fields(map, fun) when is_map(map) do | ||
Enum.into(map, %{}, &expand_fields(&1, fun)) | ||
end | ||
|
||
@spec expand_fields(tuple, function) :: tuple | ||
def expand_fields({key, value}, fun) when is_map(value) do | ||
{fun.(key), expand_fields(value, fun)} | ||
end | ||
|
||
@spec expand_fields(tuple, function) :: tuple | ||
def expand_fields({key, value}, fun) do | ||
{fun.(key), value} | ||
end | ||
|
||
@spec expand_fields(any, function) :: any | ||
def expand_fields(value, fun) do | ||
fun.(value) | ||
end | ||
|
||
defp normalized_underscore_to_dash_config(value) when is_boolean(value) do | ||
|
@@ -123,13 +192,20 @@ defmodule JSONAPI.Utils.String do | |
defp normalized_underscore_to_dash_config(value) when is_nil(value), do: value | ||
|
||
@doc """ | ||
The configured transformation for the API's fields. JSON:API v1 recommends | ||
using dashed fields (e.g. "good-dog", versus "good_dog"). | ||
The configured transformation for the API's fields. JSON:API v1.1 recommends | ||
using camlized fields (e.g. "goodDog", versus "good_dog"). However, we don't hold a strong | ||
opinion, so feel free to customize it how you would like (e.g. "good-dog", versus "good_dog"). | ||
|
||
This library currently supports dashed and underscored fields. | ||
This library currently supports camelized, dashed and underscored fields. | ||
|
||
## Configuration examples | ||
|
||
camelCase fields: | ||
|
||
``` | ||
config :jsonapi, field_transformation: :camelize | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ought this to be the default going forward, or do you think that explicit configuration is more ideal? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kind of goes back to this discussion but I agree it probably should be the default going forward. Other libraries like ja_serializer also assume the default according to the jsonapi spec. I'll wait for @doomspork's thoughts as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is the JSONAPI default, we should follow suit 👌 |
||
``` | ||
|
||
Dashed fields: | ||
|
||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this is strictly true, so it may be a bit misleading.
I'll start open a project board that has issues for new JSON:API v1.1 features.