Starchoice takes his name from the satellite TV company (now called Shaw Direct) because they are selling TV decoders. Since this lib is used to declare map decoders, I thought it felt appropriate to be named that way. Maybe not. Anyway.
The goal of the library is to provide a streamline process for converting String keyed maps to well defined structures. It is highly inspired by Elm's JSON decoders where you create different JSON decoders for the same data type.
For more information about creating decoder, visit the Starchoice.Decoder
module documentation.
def deps do
[
{:starchoice, "~> 0.3.0"}
]
end
Examples:
- Snowhite: Snowhite uses Starchoice to decode HTTP responses from APIs.
You can define decoders in your struct's module by doing the following (this is the macro approach).
defmodule User do
defstruct email: nil, password: nil, profile: nil, permissions: []
use Starchoice.Decoder
defdecoder do
field(:email, with: &String.downcase/1)
field(:password)
field(:profile, with: Profile)
field(:permissions, with: {Permission, :simple})
end
defdecoder :simple do
field(:email)
end
end
defmodule Profile do
defstruct address: nil
defdecoder do
field(:address)
end
end
defmodule Permission do
defstruct name: nil, access: nil
defdecoder :simple do
field(:name)
field(:access, with: &Permission.decode_access/1)
end
def decode_access("r"), do: :read
def decode_access("w"), do: :write
def decode_access("rw"), do: :full
end
We can now easily decode map payloads:
input = %{
"email" => "NICOLAS@nboisvert.com",
"password" => "noneofyourbusiness",
"profile" => %{
"address" => "Somewhere str."
},
"permissions" => [
%{"name" => "Articles", "access" => "rw"}
%{"name" => "Settings", "access" => "r"}
]
}
{:ok, decoded} = Starchoice.decode(input, User)
%User{
email: "nicolas@nboisvert.com",
password: "noneofyourbusiness",
profile: %Profile{
address: "Somewhere str."
},
permissions: [
%Permission{name: "Articles", access: :full},
%Permission{name: "Settings", access: :read},
]
}
The basic of this can easily be achieved by using Ecto. However, for building a HTTP client or packaging lib, it might be a bit overkill to import a whole library like Ecto. This lightweight package can be pretty handy and is quite extensible.
This is something that might become helpful. Have for an instance, an API that returns every results under a results
key like {"results": [{}, {}, ...]}
. It would be pretty useful to have a polymorphic decoder. It is supported out of the box by doing the following:
defmodule Results do
defstruct results: []
def decoder(sub_type) do
__MODULE__
|> Starchoice.Decoder.new()
|> Starchoice.Decoder.put_field(:results, sub_type)
end
end
Then you can use it like that:
input = %{"results" => [%{"email" => "email@email.com"}, %{"email" => "another_email@email.com"}]}
Starchoice.decode(input, Results.decoder({User, :simple})) # this uses the :simple decoder defined for User before.
%Results{
results: %{
%User{email: "email@email.com"},
%User{email: "another_email@email.com"},
}
}
This source code is licensed under the MIT license. Copyright (c) 2020-present Nicolas Boisvert.