diff --git a/lib/poison/parser.ex b/lib/poison/parser.ex index a935777c..7489100b 100644 --- a/lib/poison/parser.ex +++ b/lib/poison/parser.ex @@ -60,8 +60,9 @@ defmodule Poison.Parser do def parse!(iodata, options) do string = IO.iodata_to_binary(iodata) keys = Map.get(options, :keys) + format_datetime = Map.get(options, :format_datetime) {rest, pos} = skip_whitespace(skip_bom(string), 0) - {value, pos, rest} = value(rest, pos, keys) + {value, pos, rest} = value(rest, pos, keys, format_datetime) case skip_whitespace(rest, pos) do {"", _pos} -> value @@ -72,41 +73,48 @@ defmodule Poison.Parser do reraise %ParseError{value: iodata}, stacktrace() end - defp value("\"" <> rest, pos, _keys) do - string_continue(rest, pos + 1, []) + defp value("\"" <> rest, pos, _keys, format_datetime) do + {parsed_str, pos, rest} = string_continue(rest, pos + 1, []) + maybe_parsed_date = parse_date(parsed_str, format_datetime) + {maybe_parsed_date, pos, rest} end - defp value("{" <> rest, pos, keys) do + defp value("{" <> rest, pos, keys, format_datetime) do {rest, pos} = skip_whitespace(rest, pos + 1) - object_pairs(rest, pos, keys, []) + object_pairs(rest, pos, keys, format_datetime, []) end - defp value("[" <> rest, pos, keys) do + defp value("[" <> rest, pos, keys, _format_datetime) do {rest, pos} = skip_whitespace(rest, pos + 1) array_values(rest, pos, keys, []) end - defp value("null" <> rest, pos, _keys), do: {nil, pos + 4, rest} - defp value("true" <> rest, pos, _keys), do: {true, pos + 4, rest} - defp value("false" <> rest, pos, _keys), do: {false, pos + 5, rest} + defp value("null" <> rest, pos, _keys, _format_datetime), + do: {nil, pos + 4, rest} - defp value(<> = string, pos, _keys) + defp value("true" <> rest, pos, _keys, _format_datetime), + do: {true, pos + 4, rest} + + defp value("false" <> rest, pos, _keys, _format_datetime), + do: {false, pos + 5, rest} + + defp value(<> = string, pos, _keys, _format_datetime) when char in '-0123456789' do number_start(string, pos) end - defp value(other, pos, _keys), do: syntax_error(other, pos) + defp value(other, pos, _keys, _format_datetime), do: syntax_error(other, pos) ## Objects - defp object_pairs("\"" <> rest, pos, keys, acc) do + defp object_pairs("\"" <> rest, pos, keys, format_datetime, acc) do {name, pos, rest} = string_continue(rest, pos + 1, []) {value, start, pos, rest} = case skip_whitespace(rest, pos) do {":" <> rest, start} -> {rest, pos} = skip_whitespace(rest, start + 1) - {value, pos, rest} = value(rest, pos, keys) + {value, pos, rest} = value(rest, pos, keys, format_datetime) {value, start, pos, rest} {other, pos} -> @@ -118,7 +126,7 @@ defmodule Poison.Parser do case skip_whitespace(rest, pos) do {"," <> rest, pos} -> {rest, pos} = skip_whitespace(rest, pos + 1) - object_pairs(rest, pos, keys, acc) + object_pairs(rest, pos, keys, format_datetime, acc) {"}" <> rest, pos} -> {:maps.from_list(acc), pos + 1, rest} @@ -128,11 +136,11 @@ defmodule Poison.Parser do end end - defp object_pairs("}" <> rest, pos, _, []) do + defp object_pairs("}" <> rest, pos, _, _, []) do {:maps.new(), pos + 1, rest} end - defp object_pairs(other, pos, _, _), do: syntax_error(other, pos) + defp object_pairs(other, pos, _, _, _), do: syntax_error(other, pos) defp object_name(name, pos, :atoms!) do String.to_existing_atom(name) @@ -151,7 +159,7 @@ defmodule Poison.Parser do end defp array_values(string, pos, keys, acc) do - {value, pos, rest} = value(string, pos, keys) + {value, pos, rest} = value(string, pos, keys, false) acc = [value | acc] @@ -356,4 +364,38 @@ defmodule Poison.Parser do defp syntax_error(_, pos) do raise %ParseError{pos: pos, value: ""} end + + defp parse_date(parsed_str, :date) do + with {:ok, date} <- Date.from_iso8601(parsed_str) do + date + else + _ -> parsed_str + end + end + + defp parse_date(parsed_str, :datetime) do + with {:ok, datetime, _} <- DateTime.from_iso8601(parsed_str) do + datetime + else + _ -> parsed_str + end + end + + defp parse_date(parsed_str, :time) do + with {:ok, time} <- Time.from_iso8601(parsed_str) do + time + else + _ -> parsed_str + end + end + + defp parse_date(parsed_str, :naive_datetime) do + with {:ok, naive_datetime} <- NaiveDateTime.from_iso8601(parsed_str) do + naive_datetime + else + _ -> parsed_str + end + end + + defp parse_date(parsed_str, _), do: parsed_str end diff --git a/test/poison/parser_test.exs b/test/poison/parser_test.exs index 49bd0c76..97dc477b 100644 --- a/test/poison/parser_test.exs +++ b/test/poison/parser_test.exs @@ -198,6 +198,81 @@ defmodule Poison.ParserTest do assert parse!(~s({"foo": "bar"}), %{keys: :atoms}) == %{foo: "bar"} end + test "parse date" do + date = %Date{ + calendar: Calendar.ISO, + day: 10, + month: 10, + year: 2018 + } + + assert parse!("\"2018-10-10\"", %{format_datetime: :date}) == date + + assert parse!("{ \"date\": \"2018-10-10\" }", %{format_datetime: :date}) == + %{"date" => date} + end + + test "parse time" do + time = %Time{ + calendar: Calendar.ISO, + hour: 1, + minute: 8, + second: 52, + microsecond: {735_272, 6} + } + + assert parse!("\"01:08:52.735272\"", %{format_datetime: :time}) == time + + assert parse!("{ \"time\": \"01:08:52.735272\" }", %{format_datetime: :time}) == + %{"time" => time} + end + + test "parse datetime" do + datetime = %DateTime{ + calendar: Calendar.ISO, + day: 11, + hour: 0, + microsecond: {262_450, 6}, + minute: 12, + month: 10, + second: 41, + std_offset: 0, + time_zone: "Etc/UTC", + utc_offset: 0, + year: 2018, + zone_abbr: "UTC" + } + + assert parse!("\"2018-10-11T00:12:41.262450Z\"", %{ + format_datetime: :datetime + }) == datetime + + assert parse!("{ \"datetime\": \"2018-10-11T00:12:41.262450Z\" }", %{ + format_datetime: :datetime + }) == %{"datetime" => datetime} + end + + test "parse naive datetime" do + naive_datetime = %NaiveDateTime{ + calendar: Calendar.ISO, + day: 10, + hour: 1, + minute: 13, + month: 10, + second: 20, + microsecond: {712_433, 6}, + year: 2018 + } + + assert parse!("\"2018-10-10T01:13:20.712433\"", %{ + format_datetime: :naive_datetime + }) == naive_datetime + + assert parse!("{ \"naive_datetime\": \"2018-10-10T01:13:20.712433\" }", %{ + format_datetime: :naive_datetime + }) == %{"naive_datetime" => naive_datetime} + end + defp parse!(iodata) do parse!(iodata, %{}) end