jsonelixirelixir-poison

Float format when json serializing in elixir


Elixir uses scientific notation by default for floats greater then 1000. This causes an undesired side effect during json serialization.

iex(5)> val = 1000.00
1.0e3
iex(11)> Poison.encode(val)
{:ok, "1.0e3"}

The output I would like is

iex(11)> Poison.encode(val)
{:ok, "1000.00"}

I have seen this answer that uses :erlang.float_to_binary(0.005 * 2.7 / 100, [:compact, {:decimals, 10}]) or :io.format("~f~n",[0.005 * 2.7 / 100]), but this would require to patch Poison or to manually check all data before encoding.

Is there a cleaner way to force the default flot to binary format in elixir ?


Solution

  • Poison allows to implement Poison.Encoder protocol. The implementation for Float obviously exists, that’s why I’d suggest to wrap your floats into custom FloatStruct upfront:

    defmodule FloatStruct do
      defstruct value: 0.0, format: [:compact, {:decimals, 10}]
    end
    
    defimpl Poison.Encoder, for: FloatStruct do
      def encode(%{value: value, format: format}, options) do
        Poison.Encoder.BitString.encode(
          :erlang.float_to_binary(value, format), options)
      end
    end
    

    I understand that this would require traversing nested terms to wrap Floats with FloatStructs, but I don’t see any easier approach. I might be wrong, though.


    One might support both mentioned in the OP formats for free:

    defimpl Poison.Encoder, for: FloatStruct do
      def encode(%{value: value, format: format}, options) do
        Poison.Encoder.BitString.encode(
          format(value, format), options)
      end
    
      defp format(value, format) when is_list(format),
        do: :erlang.float_to_binary(value, format)
      defp format(value, format) when is_binary(format),
        do: :io.format(format, [value])
    end