Mold - a tiny, zero-dependency parsing library for external payloads

Mold parses JSON APIs, webhooks, HTTP params and other external input into clean Elixir terms - coerces types, renames keys, checks structure, and returns {:ok, result} or {:error, errors} with traces.

Cheatsheet | Documentation | Hex

Mold.parse(:integer, "42")              #=> {:ok, 42}
Mold.parse(:date, "2024-01-02")         #=> {:ok, ~D[2024-01-02]}

Mold.parse(%{name: :string, age: :integer}, %{"name" => "Alice", "age" => "25"})
#=> {:ok, %{name: "Alice", age: 25}}

# Options refine the type
Mold.parse({:integer, min: 0, max: 100}, "50")              #=> {:ok, 50}
Mold.parse({:atom, in: [:draft, :published]}, "draft")       #=> {:ok, :draft}

# Any function works as a type
Mold.parse(&Version.parse/1, "1.0.0")
#=> {:ok, %Version{major: 1, minor: 0, patch: 0}}

# Errors include the path to the failing value
Mold.parse(%{items: [%{name: :string}]}, %{"items" => [%{"name" => "A"}, %{}]})
#=> {:error, [%Mold.Error{reason: {:missing_field, "name"}, trace: ["items", 1], ...}]}

Types are plain data - atoms, tuples, maps, functions. No macros, no structs. A type is just a value you can build at runtime, store in a variable, or compose dynamically.

Mold follows the Parse, don’t validate approach. You parse at the boundary, and from that point on you work with clean Elixir terms. Mold handles structural correctness - business logic is a separate layer.

Also supports: source key mapping with propagation, union types, recursive types, and shared options (nilable, default, in, transform, validate) on all types.

Happy to answer any questions :slightly_smiling_face:

8 Likes

I’ve been having a few of these moments lately… I was literally just thinking about needing something like and came to Elixir Forum to procrastinate thinking about it.

What’s the pitch for using this library over Ecto? Just more minimal for cases where you otherwise wouldn’t need Ecto? How about other dedicated parsing libs?

2 Likes

You might want instead of hardcoded opinionated coercers allow coercer: and validator: options (with proper defaults.)

You might find some inspiration in estructura on that matter.