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


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="andreashasse" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/andreashasse/120/40185_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  andreashasse
                    <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-388067-spectral-update-struct-defaults-field-filtering-codec-helpers-and-a-compile-time-speedup-1" class="anchor" href="#p-388067-spectral-update-struct-defaults-field-filtering-codec-helpers-and-a-compile-time-speedup-1" aria-label="Heading link" rel="nofollow"></a>Spectral update: struct defaults, field filtering, codec helpers, and a compile-time speedup</h2>
<h2><a name="p-388067-struct-defaults-on-decode-2" class="anchor" href="#p-388067-struct-defaults-on-decode-2" aria-label="Heading link" rel="nofollow"></a>Struct defaults on decode</h2>
<p>Missing JSON fields now fill from the struct’s own default values (<code>__struct__/0</code>) rather than always defaulting to <code>nil</code>. Non-nullable fields with a <code>nil</code> default still produce a <code>missing_data</code> error.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Job do
 use Spectral
 defstruct timeout: 30, retries: 3
 @type t :: %Job{timeout: pos_integer(), retries: non_neg_integer()}
end

Spectral.decode(~s({}), Job, :t)
#=&gt; {:ok, %Job{timeout: 30, retries: 3}}
</code></pre>
<h3><a name="p-388067-field-filtering-3" class="anchor" href="#p-388067-field-filtering-3" aria-label="Heading link" rel="nofollow"></a>Field filtering</h3>
<p>New only key in <span class="mention">@spectral</span> accepts a list of field atoms, restricting which fields participate in encode, decode, and schema generation. Excluded fields are filled from defaults on decode and ignored on encode. Useful for computed or internal fields you never want in the API.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule User do
  use Spectral
  defstruct [:name, :email, role: :viewer]
  spectral(only: [:name, :role])
  @type t :: %User{name: String.t(), email: String.t() | nil, role: atom()}
end

Spectral.encode(%User{name: "Alice", email: "alice@example.com", role: :admin}, User, :t)
#=&gt; {:ok, ~s({"name":"Alice","role":"admin"})}   # email excluded

Spectral.decode(~s({"name":"Alice","email":"alice@example.com"}), User, :t)
#=&gt; {:ok, %User{name: "Alice", email: nil, role: :viewer}}  # email ignored, role filled from default
</code></pre>
<h3><a name="p-388067-built-in-string-codec-4" class="anchor" href="#p-388067-built-in-string-codec-4" aria-label="Heading link" rel="nofollow"></a>Built-in String codec</h3>
<p><code>String.t()</code> encoding and decoding is now handled by a dedicated built-in codec rather than by reading the String module’s type definitions from its BEAM file (even when cached, this is now eliminated entirely). Constraint validation (min_length, max_length, pattern, format) works as before.</p>
<h3><a name="p-388067-compile-time-type-info-5" class="anchor" href="#p-388067-compile-time-type-info-5" aria-label="Heading link" rel="nofollow"></a>Compile-time type info</h3>
<p>Previously, use Spectral injected a <code>spectra_type_info/0</code> that built type information from the module’s BEAM abstract code at runtime (cached via persistent_term). Now the type information is fully built at compile time and baked into the generated function as a constant — no runtime work at all. No API changes needed.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="388067" 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/spectral-type-driven-json-encoding-decoding-validation-and-openapi-generation/74902/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-388067" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="388067"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>