wrren

wrren

Want - Type Conversion and Coercion Library

I wrote this library several years ago, but I’ve recently had a use case for it that’s caused me to make several updates that I think would also make it quite useful for others. Initially, I wanted a library that let me convert between types in Erlang and Elixir in a logical and consistent way. Over time, I’ve extended it to try and solve the general problem of coercing complex data from various sources into well-defined structures. On a basic level, want lets you convert between scalar types.

# String to integer
{:ok, 1} = Want.integer("1")
# String to integer, raise on failure
1 = Want.integer!("1")
# String to float
{:ok, 1.0} = Want.float("1")
# String to float, raise on failure
1.0 = Want.float!("1")
# Integer to string
{:ok, "1"} = Want.string(1)
# Integer to string, raise on failure
"1" = Want.string!(1)
# String to boolean
{:ok, true} = Want.boolean("true")
{:ok, false} = Want.boolean("FALSE")
# String to boolean, raise on failure
true = Want.boolean!("true")

However where want really shines is in defining structures and casting incoming data into them. This is similar to what Ecto.Schema can accomplish, but in a more generic way and with more options that let you deal with dirty data.

First you can define a schema for conversion using a map, and then convert incoming data into a map or keyword list like so:

@schema %{
  hello:    [type: :string, from: "world"],
  foo:       [type: :integer, default: 0]
}

{:ok, %{hello: "goodbye", foo: 0}} = Want.map(%{"world" => "goodbye"}, @schema) 

In this simple example you can see how a schema lets you define source fields, types, defaults, etc. We can make this more complex like so:

@schema %{
  hello:     [type: :string, from: "world", transform: %String.upcase/1],
  foo:        [type: :integer, default: 0, from: {"a", "b", "c"}]
}

{:ok, %{hello: "GOODBYE", foo: 10}} = Want.map(%{"world" => "goodbye", "a" => %{"b" => %{"c" => 10}}}, @schema) 

Here you can see field transformations in action, which are applied after a field has been cast, as well as sourcing data from nested fields.

Finally we have shape/1, which is a macro that lets us define a schema and struct at the same time, exposing functions for casting from data or lists of data.

defmodule MyShape do
  use Want.Shape

  shape transform: &__MODULE__.transform/1 do
    field :is_valid,       :boolean,   default: false
    field :count,          :integer,   default: 0
    field :from,            :string,    from: "FromField"
    field :multi_from,  :integer,   from: ["a", {"b", "c", "d"}], default: 0
  end

  def transform(%MyShape{count: c} = s),
    do: %{s | count: c * 2}
end

{:ok, %MyModule{is_valid: true, count: 20, from: "Foo", multi_from: 10}} = MyShape.cast(%{
    "is_valid"  => "true",
    "count"     => "10",
    "from"      => "Foo",
    "b"         => %{
        "c" => %{
            "d" => 10
        }
    }
})

As with map schemas, you can define types and options. shape also lets you specify a transformation function that’s applied to the entire result after casting. use Want.Shape exposes the cast/1, cast!/1, cast_all/1 and cast_all!/1 functions as part of your module.

Want on hex.pm
Want on github:

Most Liked

wrren

wrren

Custom type support has been added. You just need to implement the Want.Type behaviour and it will work. See the updated README for more details.

Eiji

Eiji

You forgot to create a PR :slight_smile:

For CLI I would also suggest:

  1. y (short for yes)
  2. t (short for true)
  3. n (short for no)
  4. f (short for false)

I use them more often than 0 and 1.

cond do
  String.downcase(value) in ~w"true t yes y 1" -> {:ok, true}
  String.downcase(value) in ~w"false f no n 0" -> {:ok, false}
  true -> {:error, "Failed to convert value #{inspect(value)} to boolean."}
end
Eiji

Eiji

What I generally advise is to use common naming that you can find in Elixir, ecto or phoenix. In this case I recommend to use: source instead of from as it’s already well known in ecto, see: :source option in: Options | Ecto.Schema.field/3. :books:

I like the idea of flexibility between passing schema map and using DSL. :+1:

mayel

mayel

Thanks for sharing, this looks handy!

Is there a way to define custom types to to extend the type parsing rules? In the meantime, here’s a quick PR, as I can see this being a quick win for parsing ENV variables for config: Comparing wrren:master...mayel:patch-1 · wrren/want.erl · GitHub

code-shoily

code-shoily

Lol looking at the title, I thought you “wanted” a library for this and this was a wish list :slight_smile:

It looks good.

Where Next?

Popular in Libraries Top

scohen
Lexical Lexical is a next-generation language server for the Elixir programming language. Features Context aware code completion As-you...
New
hpopp
After just over two years in development, this latest version of Pigeon is what I finally consider done in regards to my original vision ...
New
kevinlang
Hey all, We have made an Ecto3 Adapter for SQLite3, ecto_sqlite3! We have successfully on-boarded the full suite of integration tests (...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
achempion
Hi, I would like to tell about my initiative to further maintain and develop Waffle project which is the fork of Arc library. The progre...
New
michalmuskala
Hello everybody. I have just released Jason - a new JSON library. You might be wondering, why do we need a new library? The primary foc...
New
Qqwy
Solution is a library to help you with working with ok/error-tuples in case and with-expressions by exposing special matching macros, as ...
New
zorbash
I created Kitto a framework for dashboards inspired by Dashing. [demo] The distributed characteristics of Elixir and the low memory foo...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
Qqwy
While not as prevalent as in imperative languages, arrays (collections with efficient random element access) are still very useful in Eli...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New

Sub Categories:

We're in Beta

About us Mission Statement