Skip to content

Commit f7cab3c

Browse files
author
Manos Emmanouilidis
committed
camel case map key formatting
1 parent a78814c commit f7cab3c

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

lib/poison/encoder.ex

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ defmodule Poison.Encode do
2222
alias String.Chars
2323
alias Poison.EncodeError
2424

25-
@compile {:inline, encode_name: 1}
25+
@compile {:inline, encode_name: 2}
2626

2727
# Fast path encoding string keys
28-
defp encode_name(value) when is_binary(value) do
28+
defp encode_name(value, :default) when is_binary(value) do
2929
value
3030
end
3131

32-
defp encode_name(value) do
32+
defp encode_name(value, :default) do
3333
case Chars.impl_for(value) do
3434
nil ->
3535
raise EncodeError, value: value,
@@ -38,6 +38,23 @@ defmodule Poison.Encode do
3838
impl.to_string(value)
3939
end
4040
end
41+
42+
defp encode_name(value, :camel_case) when is_binary(value) do
43+
value
44+
|> String.split("_", trim: true)
45+
|> camelize(:first)
46+
|> Enum.join
47+
end
48+
49+
defp encode_name(value, :camel_case) do
50+
value
51+
|> encode_name(:default)
52+
|> encode_name(:camel_case)
53+
end
54+
55+
defp camelize([], _), do: []
56+
defp camelize([h | t], :first), do: [String.downcase(h) | camelize(t, :rest)]
57+
defp camelize([h | t], _), do: [String.capitalize(h), camelize(t, :rest)]
4158
end
4259
end
4360
end
@@ -83,13 +100,15 @@ defprotocol Poison.Encoder do
83100
@typep indent :: non_neg_integer
84101
@typep offset :: non_neg_integer
85102
@typep strict_keys :: boolean
103+
@typep format_keys :: :default | :camel_case
86104

87105
@type options :: %{
88106
optional(:escape) => escape,
89107
optional(:pretty) => pretty,
90108
optional(:indent) => indent,
91109
optional(:offset) => offset,
92110
optional(:strict_keys) => strict_keys,
111+
optional(:format_keys) => format_keys
93112
}
94113

95114
@spec encode(t, options) :: iodata
@@ -235,39 +254,55 @@ defimpl Poison.Encoder, for: Map do
235254

236255
def encode(map, options) do
237256
map
238-
|> strict_keys(Map.get(options, :strict_keys, false))
257+
|> strict_keys(Map.get(options, :strict_keys, false), options[:format_keys] || :default)
239258
|> encode(pretty(options), options)
240259
end
241260

242261
def encode(map, true, options) do
243262
indent = indent(options)
244263
offset = offset(options) + indent
245264
options = offset(options, offset)
265+
key_formatter = options[:format_keys] || :default
246266

247-
fun = &[",\n", spaces(offset), Encoder.BitString.encode(encode_name(&1), options), ": ",
267+
fun = &[",\n", spaces(offset), Encoder.BitString.encode(encode_name(&1, key_formatter), options), ": ",
248268
Encoder.encode(:maps.get(&1, map), options) | &2]
249269
["{\n", tl(:lists.foldl(fun, [], :maps.keys(map))), ?\n, spaces(offset - indent), ?}]
250270
end
251271

252272
def encode(map, _, options) do
253-
fun = &[?,, Encoder.BitString.encode(encode_name(&1), options), ?:,
273+
key_formatter = options[:format_keys] || :default
274+
fun = &[?,, Encoder.BitString.encode(encode_name(&1, key_formatter), options), ?:,
254275
Encoder.encode(:maps.get(&1, map), options) | &2]
255276
[?{, tl(:lists.foldl(fun, [], :maps.keys(map))), ?}]
256277
end
257278

258-
defp strict_keys(map, false), do: map
259-
defp strict_keys(map, true) do
279+
defp strict_keys(map, false, _), do: map
280+
defp strict_keys(map, true, :default) do
260281
map
261282
|> Map.keys
262283
|> Enum.each(fn key ->
263-
name = encode_name(key)
284+
name = encode_name(key, :default)
264285
if Map.has_key?(map, name) do
265286
raise EncodeError, value: name,
266287
message: "duplicate key found: #{inspect(key)}"
267288
end
268289
end)
269290
map
270291
end
292+
293+
defp strict_keys(map, true, :camel_case) do
294+
map
295+
|> Map.keys
296+
|> Enum.reduce(%{}, fn (key, acc) ->
297+
name = encode_name(key, :camel_case)
298+
if Map.has_key?(acc, name) do
299+
raise EncodeError, value: name,
300+
message: "duplicate key found: #{inspect(key)}"
301+
end
302+
Map.put(acc, name, :ok)
303+
end)
304+
map
305+
end
271306
end
272307

273308
defimpl Poison.Encoder, for: List do

test/poison/encoder_test.exs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,24 @@ defmodule Poison.EncoderTest do
192192
end
193193
end
194194

195+
test "camelCase" do
196+
camel_case = [format_keys: :camel_case]
197+
198+
assert to_json(%{one_two: true}, camel_case) == ~s({"oneTwo":true})
199+
assert to_json(%{One_two_three: true}, camel_case) == ~s({"oneTwoThree":true})
200+
assert to_json(%{"one_two" => true}, camel_case) == ~s({"oneTwo":true})
201+
202+
camel_and_strict = camel_case ++ [strict_keys: true]
203+
204+
assert_raise Poison.EncodeError, ~r/duplicate key found/, fn ->
205+
to_json(%{:one_two => true, "one_two" => 4}, camel_and_strict)
206+
end
207+
208+
assert_raise Poison.EncodeError, ~r/duplicate key found/, fn ->
209+
to_json(%{:one_two => true, "_one_two_" => 4}, camel_and_strict)
210+
end
211+
end
212+
195213
defp to_json(value, options \\ []) do
196214
value
197215
|> Poison.Encoder.encode(Map.new(options))

0 commit comments

Comments
 (0)