<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="378825" data-post-id="378825">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="phcurado" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/phcurado/120/39030_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  phcurado
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<h3><a name="p-378825-zoi-is-reaching-10k-downloads-on-hexpmhttpshexpmpackageszoi-star-1" class="anchor" href="#p-378825-zoi-is-reaching-10k-downloads-on-hexpmhttpshexpmpackageszoi-star-1" aria-label="Heading link" rel="nofollow"></a><code>Zoi</code> is reaching 10k downloads on <a href="https://hex.pm/packages/zoi" rel="nofollow">hex.pm</a> <img src="https://forum.elixirforum.com/images/emoji/apple/star.png?v=15" title=":star:" class="emoji" alt=":star:" loading="lazy" width="20" height="20"></h3>
<p>I would like to thank everyone who has used and supported this library. Every feedback has been taken seriously and helped to improve this project.</p>
<p>These are the main features that have been added based on feedback:</p>
<ul>
<li><a href="https://hexdocs.pm/zoi/Zoi.Describe.html" rel="noopener nofollow ugc">Zoi.describe/2</a>: function for generating documentation based on descriptions your schema.</li>
<li><a href="https://hexdocs.pm/zoi/Zoi.Form.html" rel="noopener nofollow ugc">Zoi.Form</a>: module that integrates with Phoenix forms.</li>
<li><a href="https://hexdocs.pm/zoi/Zoi.JSONSchema.html" rel="noopener nofollow ugc">Zoi.JSONSchema</a>: Converting Zoi schemas to JSON Schema (for API documentation, validation, etc.).</li>
<li><a href="https://hexdocs.pm/zoi/Zoi.Struct.html" rel="noopener nofollow ugc">Zoi.Struct</a>: A helper module to define and validate structs using Zoi schemas</li>
<li><a href="https://hexdocs.pm/zoi/Zoi.Schema.html" rel="noopener nofollow ugc">Zoi.Schema</a>: Utilities for traversing and transforming Zoi schemas.</li>
</ul>
<p>And many more added into the project’s internals!</p>
<p>I created a blog post where I walk through some of the <code>Zoi</code> features and the reason behind creating this library.</p>
<p><a href="https://paulocurado.com/blog/zoi-a-schema-validation-library-for-elixir-inspired-by-zod/" rel="noopener nofollow ugc">Check it out here</a>.</p>
<p>Don’t forget to check the <a href="https://github.com/phcurado/zoi" rel="noopener nofollow ugc">git repo</a> and if you have any feedback or suggestions, feel free to open an issue or a PR.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="378825" data-batch-url="/posts/batch_likers">
                        9
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/22">Post #21</a>
	                </div>
	            </div>
              <div id="likers-container-378825" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="378825"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #21"></div>
  </section>
</div>
    <div class="postbit" id="378838" data-post-id="378838">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="lud" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lud/120/14382_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  lud
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Hello <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20">  and congrats!</p>
<p>Thanks again for featuring Oaskit!</p>
<p>How hard was it to implement the typespec generation, like for instance for a map where values are lists of integers? I’m thinking of implementing this for the JSON schema validator but I fear it will be too complicated, or otherwise the types will be too broad (which can be good enough…).</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="378838" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/23">Post #22</a>
	                </div>
	            </div>
              <div id="likers-container-378838" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="378838"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #22"></div>
  </section>
</div>
    <div class="postbit" id="378841" data-post-id="378841">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="phcurado" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/phcurado/120/39030_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  phcurado
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>thank you <a class="mention" href="/u/lud" rel="nofollow">@lud</a> for your great libray as well!</p>
<aside class="quote no-group" data-username="lud" data-post="23" data-topic="72108">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lud/48/14382_2.png" class="avatar"> lud:</div>
<blockquote>
<p>How hard was it to implement the typespec generation, like for instance for a map where values are lists of integers? I’m thinking of implementing this for the JSON schema validator but I fear it will be too complicated, or otherwise the types will be too broad (which can be good enough…).</p>
</blockquote>
</aside>
<p>It was not that hard but quite tedious since <code>Zoi</code> supports many types. A few examples:</p>
<ul>
<li><code>Zoi.string/1</code> is simply <code>binary()</code> under the hood, check <a href="https://github.com/phcurado/zoi/blob/main/lib/zoi/types/string.ex#L73" rel="noopener nofollow ugc">here</a>.</li>
<li><code>Zoi.union/2</code> loops through the list of schemas and add the <code>|</code> operator, check <a href="https://github.com/phcurado/zoi/blob/main/lib/zoi/types/union.ex#L49" rel="noopener nofollow ugc">here</a></li>
<li><code>Zoi.object/2</code>, one of the most complex types, is a basic loop that calls the type_spec function recursively, <a href="https://github.com/phcurado/zoi/blob/main/lib/zoi/types/object.ex#L50" rel="noopener nofollow ugc">here</a>.</li>
</ul>
<p>So if you build a map like this (map where the value is a list of integers):</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">@schema Zoi.object(%{numbers: Zoi.array(Zoi.integer())})

@type schema :: unquote(Zoi.type_spec(@schema))
</code></pre>
<p>The type would have the following AST:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">{:%{}, [], [{{:required, [], [:numbers]}, [{:integer, [], []}]}]}
</code></pre>
<p>which translates to:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">%{numbers: [integer()]}
</code></pre>
<p>So quite precise in terms of how the type should be according to your schema</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="378841" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/24">Post #23</a>
	                </div>
	            </div>
              <div id="likers-container-378841" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="378841"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #23"></div>
  </section>
</div>
    <div class="postbit" id="378842" data-post-id="378842">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="lud" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lud/120/14382_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  lud
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>It’s good that you have a struct for each type, so you can directly map the struct to a typespec in many cases.</p>
<p>I’ll have to infer a lot more things, but I can take some inspiration from your code <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>Thank you!</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="378842" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/25">Post #24</a>
	                </div>
	            </div>
              <div id="likers-container-378842" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="378842"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #24"></div>
  </section>
</div>
    <div class="postbit" id="379377" data-post-id="379377">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="cmo" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/cmo/120/19618_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  cmo
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Can you define a schema that includes itself?  Something like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">  embedded_schema do
    field :_type, Ecto.Enum, values: @types

    # rule
    field :field, Ecto.Enum, values: @fields
    field :operator, Ecto.Enum, values: @operators
    field :value, :string

    # rules
    field :condition, Ecto.Enum, values: @conditions
    embeds_many :rules, __MODULE__, on_replace: :delete
  end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="379377" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/26">Post #25</a>
	                </div>
	            </div>
              <div id="likers-container-379377" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="379377"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #25"></div>
  </section>
</div>
    <div class="postbit" id="379387" data-post-id="379387">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="phcurado" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/phcurado/120/39030_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  phcurado
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>right now self-referencing schemas is not possible but that’s a good idea. I created an <a href="https://github.com/phcurado/zoi/issues/119" rel="noopener nofollow ugc">issue</a> and I will implement</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="379387" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/27">Post #26</a>
	                </div>
	            </div>
              <div id="likers-container-379387" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="379387"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #26"></div>
  </section>
</div>
    <div class="postbit" id="380323" data-post-id="380323">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="phcurado" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/phcurado/120/39030_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  phcurado
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<h2><a name="p-380323-new-release-1" class="anchor" href="#p-380323-new-release-1" aria-label="Heading link" rel="nofollow"></a>New release <img src="https://forum.elixirforum.com/images/emoji/apple/rocket.png?v=15" title=":rocket:" class="emoji" alt=":rocket:" loading="lazy" width="20" height="20"></h2>
<p><strong>Zoi v0.13.0</strong> is out with codecs for bidirectional transformations and new refinements!</p>
<h3><a name="p-380323-codecs-2" class="anchor" href="#p-380323-codecs-2" aria-label="Heading link" rel="nofollow"></a>Codecs</h3>
<p>Codecs enable parsing from one type to another and encoding back. This is useful when you need to work with different representations of the same data:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; date_codec = Zoi.codec(
...&gt;   Zoi.ISO.date(),
...&gt;   Zoi.date(),
...&gt;   decode: fn value -&gt; Date.from_iso8601(value) end,
...&gt;   encode: fn value -&gt; Date.to_iso8601(value) end
...&gt; )

iex&gt; Zoi.parse(date_codec, "2025-01-15")
{:ok, ~D[2025-01-15]}

iex&gt; Zoi.encode(date_codec, ~D[2025-01-15])
{:ok, "2025-01-15"}
</code></pre>
<p>Also added <code>Zoi.encode!/3</code> for when you want to raise on errors.</p>
<h3><a name="p-380323-multiple_of-refinement-3" class="anchor" href="#p-380323-multiple_of-refinement-3" aria-label="Heading link" rel="nofollow"></a>multiple_of refinement</h3>
<p>New <code>multiple_of/3</code> for validating that a number is divisible by a given value. Works with <code>integer</code>, <code>float</code>, <code>number</code>, and <code>decimal</code>:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; schema = Zoi.integer() |&gt; Zoi.multiple_of(5)
iex&gt; Zoi.parse(schema, 10)
{:ok, 10}
iex&gt; Zoi.parse(schema, 7)
{:error, [%Zoi.Error{code: :not_multiple_of, message: "must be a multiple of 5"}]}
</code></pre>
<h3><a name="p-380323-lazy-type-4" class="anchor" href="#p-380323-lazy-type-4" aria-label="Heading link" rel="nofollow"></a>Lazy type</h3>
<p><code>Zoi.lazy/1</code> enables recursive and self-referencing schemas by deferring evaluation:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; category_schema = Zoi.object(%{
...&gt;   name: Zoi.string(),
...&gt;   subcategories: Zoi.array(Zoi.lazy(fn -&gt; category_schema end)) |&gt; Zoi.optional()
...&gt; })

iex&gt; input = %{
...&gt;   name: "Electronics",
...&gt;   subcategories: [
...&gt;     %{name: "Phones", subcategories: []},
...&gt;     %{name: "Laptops"}
...&gt;   ]
...&gt; }

iex&gt; Zoi.parse(category_schema, input)
{:ok, %{name: "Electronics", subcategories: [%{name: "Phones", subcategories: []}, %{name: "Laptops"}]}}
</code></pre>
<h3><a name="p-380323-typespec-protocol-5" class="anchor" href="#p-380323-typespec-protocol-5" aria-label="Heading link" rel="nofollow"></a>TypeSpec protocol</h3>
<p><code>Zoi.TypeSpec</code> is now a separate opt-in protocol. Custom types can implement it independently from <code>Zoi.Type</code>:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defimpl Zoi.TypeSpec do
  def spec(_schema, _opts) do
    quote(do: binary())
  end
end
</code></pre>
<h3><a name="p-380323-json-schema-strict-objects-6" class="anchor" href="#p-380323-json-schema-strict-objects-6" aria-label="Heading link" rel="nofollow"></a>JSON Schema strict objects</h3>
<p><code>Zoi.JSONSchema.Encoder</code> for objects now respects the <code>strict</code> option. When <code>strict: true</code>, the generated JSON Schema will have <code>additionalProperties: false</code>.</p>
<hr>
<p><img src="https://forum.elixirforum.com/images/emoji/apple/books.png?v=15" title=":books:" class="emoji" alt=":books:" loading="lazy" width="20" height="20"> Docs: <a href="https://hexdocs.pm/zoi" rel="noopener nofollow ugc">hexdocs.pm/zoi</a><br>
<img src="https://forum.elixirforum.com/images/emoji/apple/package.png?v=15" title=":package:" class="emoji" alt=":package:" loading="lazy" width="20" height="20"> Hex: <a href="https://hex.pm/packages/zoi" rel="nofollow">hex.pm/packages/zoi</a><br>
<img src="https://forum.elixirforum.com/images/emoji/apple/laptop.png?v=15" title=":laptop:" class="emoji" alt=":laptop:" loading="lazy" width="20" height="20"> Repo: <a href="https://github.com/phcurado/zoi" rel="noopener nofollow ugc">github.com/phcurado/zoi</a></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="380323" data-batch-url="/posts/batch_likers">
                        5
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/28">Post #27</a>
	                </div>
	            </div>
              <div id="likers-container-380323" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="380323"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #27"></div>
  </section>
</div>
    <div class="postbit" id="381367" data-post-id="381367">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="phcurado" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/phcurado/120/39030_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  phcurado
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<h2><a name="p-381367-new-0150-release-1" class="anchor" href="#p-381367-new-0150-release-1" aria-label="Heading link" rel="nofollow"></a>New 0.15.0 release</h2>
<p>Hi everyone!</p>
<p>This new version brings more types and more flexibility with elixir typespecs.</p>
<h3><a name="p-381367-new-typespec-keyword-2" class="anchor" href="#p-381367-new-typespec-keyword-2" aria-label="Heading link" rel="nofollow"></a>New <code>typespec</code> keyword:</h3>
<pre data-code-wrap="elixir"><code class="lang-elixir">Zoi.integer(gte: 0, typespec: quote(do: non_neg_integer()))
Zoi.any(typespec: quote(do: pos_integer()))
</code></pre>
<p>If you have a more complex schema which cannot be fully expressed by the Zoi inferred typespec, you can use this approach to express your type the way you want.</p>
<h3><a name="p-381367-new-types-3" class="anchor" href="#p-381367-new-types-3" aria-label="Heading link" rel="nofollow"></a>New types</h3>
<ul>
<li><code>Zoi.pid/1</code> type for validating pid values</li>
<li><code>Zoi.module/1</code> type for validating module values</li>
<li><code>Zoi.reference/1</code> type for validating reference values</li>
<li><code>Zoi.port/1</code> type for validating port values</li>
<li><code>Zoi.macro/1</code> type for validating quoted expressions (Macro.t())</li>
<li><code>Zoi.function/1</code> type for validating function values with optional arity constraint</li>
<li><code>Zoi.json/1</code> type for validating any JSON-compatible value (string, number, boolean, null, array, or object with string keys)</li>
</ul>
<h3><a name="p-381367-zoimap2-is-now-preferred-over-zoiobject2-4" class="anchor" href="#p-381367-zoimap2-is-now-preferred-over-zoiobject2-4" aria-label="Heading link" rel="nofollow"></a><code>Zoi.map/2</code> is now preferred over <code>Zoi.object/2</code></h3>
<p>This was a feedback from <a class="mention" href="/u/jam" rel="nofollow">@jam</a> and I was slowly improving the map type to align more with the Elixir native map. Now you can express maps the same as we could express objects with some extras, examples:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Map with string keys and values are integers
Zoi.map(Zoi.string(), Zoi.integer())

# Map with any key and values
Zoi.map()

# A map the follows a given structure (same as object):
Zoi.map(%{
  name: Zoi.string(),
  age: Zoi.integer() |&gt; Zoi.optional()
})
</code></pre>
<p>no changes were made in <code>Zoi.object/2</code>, it uses a structured <code>Zoi.map/2</code> under the hood and I don’t plan to deprecate this type.</p>
<h3><a name="p-381367-zoi-is-43-faster-when-parsing-5" class="anchor" href="#p-381367-zoi-is-43-faster-when-parsing-5" aria-label="Heading link" rel="nofollow"></a>Zoi is 43% faster when parsing</h3>
<p>I decided to do some benchmarks and found some areas for improvements. <code>Zoi</code> is even faster when parsing deeply nested data structure. Now <code>Zoi</code> parses 43% faster compared to the previous implementation. You can run the benchmarks by following the documentation <a href="https://github.com/phcurado/zoi/tree/main/bench" rel="noopener nofollow ugc">here</a>.</p>
<hr>
<p><img src="https://forum.elixirforum.com/images/emoji/apple/books.png?v=15" title=":books:" class="emoji" alt=":books:" loading="lazy" width="20" height="20"> Docs: <a href="https://hexdocs.pm/zoi" rel="noopener nofollow ugc">hexdocs.pm/zoi</a><br>
<img src="https://forum.elixirforum.com/images/emoji/apple/package.png?v=15" title=":package:" class="emoji" alt=":package:" loading="lazy" width="20" height="20"> Hex: <a href="https://hex.pm/packages/zoi" rel="nofollow">hex.pm/packages/zoi</a><br>
<img src="https://forum.elixirforum.com/images/emoji/apple/laptop.png?v=15" title=":laptop:" class="emoji" alt=":laptop:" loading="lazy" width="20" height="20"> Repo: <a href="https://github.com/phcurado/zoi" rel="noopener nofollow ugc">github.com/phcurado/zoi</a></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="381367" data-batch-url="/posts/batch_likers">
                        9
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/29">Post #28</a>
	                </div>
	            </div>
              <div id="likers-container-381367" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="381367"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #28"></div>
  </section>
</div>
    <div class="postbit" id="385047" data-post-id="385047">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="phcurado" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/phcurado/120/39030_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  phcurado
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<h2><a name="p-385047-v017-release-rocket-1" class="anchor" href="#p-385047-v017-release-rocket-1" aria-label="Heading link" rel="nofollow"></a>v0.17 release <img src="https://forum.elixirforum.com/images/emoji/apple/rocket.png?v=15" title=":rocket:" class="emoji" alt=":rocket:" loading="lazy" width="20" height="20"></h2>
<p>Hi everyone!</p>
<p>This release brings discriminated unions, better control over unrecognized keys, deprecation support, metadata for format types and improvement on typespecs for structs.</p>
<h3><a name="p-385047-discriminated-union-2" class="anchor" href="#p-385047-discriminated-union-2" aria-label="Heading link" rel="nofollow"></a>Discriminated Union</h3>
<p>A new type for creating discriminated unions based on a key field:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">cat_schema = Zoi.map(%{type: Zoi.literal("cat"), meow: Zoi.string()})
dog_schema = Zoi.map(%{type: Zoi.literal("dog"), bark: Zoi.string()})

schema = Zoi.discriminated_union(:type, [cat_schema, dog_schema])

Zoi.parse(schema, %{type: "cat", meow: "meow!"})
{:ok, %{type: "cat", meow: "meow!"}}
</code></pre>
<h3><a name="p-385047-new-unrecognized-keys-option-3" class="anchor" href="#p-385047-new-unrecognized-keys-option-3" aria-label="Heading link" rel="nofollow"></a>New unrecognized keys option</h3>
<p><code>Zoi.map/2</code>, <code>Zoi.object/2</code>, <code>Zoi.keyword/2</code>, and <code>Zoi.struct/3</code> now support an <code>unrecognized_keys</code> option to control how unknown keys are handled:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Returns an error when unrecognized keys are present (replaces strict: true)
Zoi.map(%{name: Zoi.string()}, unrecognized_keys: :error)

# Preserves unrecognized keys without validation
Zoi.map(%{name: Zoi.string()}, unrecognized_keys: :preserve)

# Preserves unrecognized keys with validation
Zoi.map(%{name: Zoi.string()}, unrecognized_keys: {:preserve, {Zoi.string(), Zoi.any()}})
</code></pre>
<p>The <code>strict</code> option is now deprecated in favor of <code>unrecognized_keys</code>.</p>
<h3><a name="p-385047-deprecated-option-4" class="anchor" href="#p-385047-deprecated-option-4" aria-label="Heading link" rel="nofollow"></a>Deprecated option</h3>
<p>All schema types now accept a <code>deprecated</code> option to emit deprecation warnings during parsing. <code>Zoi.describe/1</code> and <code>Zoi.to_json_schema/1</code> also reflect the deprecation:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">schema = Zoi.map(%{
  old_field: Zoi.string(deprecated: "use new_field instead"),
  new_field: Zoi.string()
})

Zoi.parse(schema, %{old_field: "old", new_field: "new"})
# warning: :old_field is deprecated: use new_field instead
#=&gt; {:ok, %{old_field: "old", new_field: "new"}}
</code></pre>
<h3><a name="p-385047-zoilazy1-now-supports-mfa-tuples-5" class="anchor" href="#p-385047-zoilazy1-now-supports-mfa-tuples-5" aria-label="Heading link" rel="nofollow"></a><code>Zoi.lazy/1</code> now supports MFA tuples</h3>
<p>This enables lazy schemas to be stored in module attributes and used at compile time:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">@schema Zoi.lazy({MyModule, :user_schema, []})
</code></pre>
<h3><a name="p-385047-format-types-now-support-metadata-6" class="anchor" href="#p-385047-format-types-now-support-metadata-6" aria-label="Heading link" rel="nofollow"></a>Format types now support metadata</h3>
<p><code>Zoi.email/1</code>, <code>Zoi.url/1</code>, <code>Zoi.uuid/1</code>, <code>Zoi.ipv4/1</code>, <code>Zoi.ipv6/1</code>, and <code>Zoi.hex/1</code> now forward <code>description</code>, <code>example</code>, <code>metadata</code> and <code>deprecated</code> options:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">schema = Zoi.url(description: "A URL", example: "https://example.com")

Zoi.description(schema) #=&gt; "A URL"
Zoi.example(schema)      #=&gt; "https://example.com"
</code></pre>
<h3><a name="p-385047-improvement-on-typespecs-for-structs-7" class="anchor" href="#p-385047-improvement-on-typespecs-for-structs-7" aria-label="Heading link" rel="nofollow"></a>Improvement on typespecs for structs</h3>
<p><code>Zoi.type_spec/1</code> for <code>Zoi.struct/3</code> now resolves optional fields as <code>nil</code>. <code>Zoi.default/3</code> with a <code>nil</code> value also resolves as <code>nil</code>:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">schema = Zoi.struct(User, %{
  name: Zoi.string(),
  age: Zoi.optional(Zoi.integer())
})

# Generates: %User{name: binary(), age: nil | integer()}
Zoi.type_spec(schema)
</code></pre>
<hr>
<p><img src="https://forum.elixirforum.com/images/emoji/apple/books.png?v=15" title=":books:" class="emoji" alt=":books:" loading="lazy" width="20" height="20"> Docs: <a href="https://hexdocs.pm/zoi" rel="noopener nofollow ugc">hexdocs.pm/zoi</a><br>
<img src="https://forum.elixirforum.com/images/emoji/apple/package.png?v=15" title=":package:" class="emoji" alt=":package:" loading="lazy" width="20" height="20"> Hex: <a href="https://hex.pm/packages/zoi" rel="nofollow">hex.pm/packages/zoi</a><br>
<img src="https://forum.elixirforum.com/images/emoji/apple/laptop.png?v=15" title=":laptop:" class="emoji" alt=":laptop:" loading="lazy" width="20" height="20"> Repo: <a href="https://github.com/phcurado/zoi" rel="noopener nofollow ugc">github.com/phcurado/zoi</a></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="385047" data-batch-url="/posts/batch_likers">
                        7
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/30">Post #29</a>
	                </div>
	            </div>
              <div id="likers-container-385047" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="385047"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #29"></div>
  </section>
</div>
    <div class="postbit" id="385211" data-post-id="385211">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="pjode" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/pjode/120/31180_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  pjode
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Library’s great! I’m curious how/if people are using it in a Phoenix/Ecto app and for what purposes. Validation on external input in controllers prior to calling anything in context modules?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="385211" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/zoi-schema-validation-library-inspired-by-zod/72108/31">Post #30</a>
	                </div>
	            </div>
              <div id="likers-container-385211" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="385211"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #30"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/72108/load_more?page=4">Load more posts (25 remaining)</a>
</div></template></turbo-stream>