edantic icon indicating copy to clipboard operation
edantic copied to clipboard

JSON casting and validation library based on Elixir type specifications

Build Status Coverage Status

Sponsored by FunBox

Edantic

Edantic is a library for casting «plain» JSON-originated data into Elixir data structures with nessesary validations.

Example

Assume there is a module with a corresponding struct:

defmodule Person do
  defstruct [
    :age, :name, :department
  ]

  @type first_name() :: String.t
  @type second_name() :: String.t

  @type t() :: %__MODULE__{
    age: non_neg_integer(),
    name: {first_name(), second_name()},
    department: :finance | :it
  }
end

And there is some JSON-originated data:

data = %{
  "age" => 23,
  "name" => ["girolamo", "savonarola"],
  "department" => "it"
}

With Edantic we can simultaneously validate this data and convert it into Elixir structures:


import Edantic

{:ok, person} = Edantic.cast(Person.t(), data)

person == %Person{
  age: 23,
  name: {"girolamo", "savonarola"},
  department: :it
}
data_bad_department = %{
  "age" => 23,
  "name" => ["girolamo", "savonarola"],
  "department" => "unknown"
}

{:error, error} = Edantic.cast(Person.t(), data_bad_department)

error
|> Edantic.CastError.format()
|> IO.puts()
key-value pair does not match to any of the specified for the map
  data: %{"department" => "unknown"}
  type: %Edantic.Support.Types.Person{age: non_neg_integer(), department: :finance | :it, name: {first_name(), second_name()}}}

JSON

By «JSON-originated data» is denoted all the data matching the following type t():

@type key() :: String.t()
@type value() ::
        String.t() | nil | boolean | integer() | float() | %{optional(key()) => value()} | [value()]
@type t() :: value()

Primitive convertions

Since plain data structures are rather poor, there are some automatic enrichments allowed while casting:

  • Strings can be casted to corresponding atoms "a" -> :a.
  • Lists of suitable size can be casted to tuples [1, "a"] -> {1, :a}.
  • Maps can be casted to arbitrary struct whith the same set of fields %{a: 123} -> %SomeSt{a: 123} if fields pass validations.

Usage in releases

Since type info is located in seperate beam chunks which are stripped by default, be sure your releases do not strip them.

For example, by setting strip_beams option to false.

  def project do
    [
      ...
      deps: deps(),
      releases: [
        release_name: [
          strip_beams: false,
          ...
        ]
      ]
    ]
  end

Installation

If available in Hex, the package can be installed by adding edantic to your list of dependencies in mix.exs:

def deps do
  [
    {:edantic, "~> 0.1.0"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/edantic.

License

This software is licensed under MIT License.