<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: andrew jarrett</title>
    <description>The latest articles on DEV Community by andrew jarrett (@ahrjarrett).</description>
    <link>https://dev.to/ahrjarrett</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F622634%2F1c86993f-8ee1-4e1b-90ea-ba888bdd14f4.jpeg</url>
      <title>DEV Community: andrew jarrett</title>
      <link>https://dev.to/ahrjarrett</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahrjarrett"/>
    <language>en</language>
    <item>
      <title>Do anything with a Valibot schema!</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Sat, 27 Sep 2025 08:25:41 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/do-anything-with-a-valibot-schema-515g</link>
      <guid>https://dev.to/ahrjarrett/do-anything-with-a-valibot-schema-515g</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/ahrjarrett/introducing-traversablevalibot-j3d" class="crayons-story__hidden-navigation-link"&gt;Introducing: @traversable/valibot&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/ahrjarrett" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F622634%2F1c86993f-8ee1-4e1b-90ea-ba888bdd14f4.jpeg" alt="ahrjarrett profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ahrjarrett" class="crayons-story__secondary fw-medium m:hidden"&gt;
              andrew jarrett
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                andrew jarrett
                
              
              &lt;div id="story-author-preview-content-2844725" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/ahrjarrett" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F622634%2F1c86993f-8ee1-4e1b-90ea-ba888bdd14f4.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;andrew jarrett&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/ahrjarrett/introducing-traversablevalibot-j3d" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 15 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/ahrjarrett/introducing-traversablevalibot-j3d" id="article-link-2844725"&gt;
          Introducing: @traversable/valibot
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/ahrjarrett/introducing-traversablevalibot-j3d" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;27&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/ahrjarrett/introducing-traversablevalibot-j3d#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              10&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introducing: @traversable/valibot</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Mon, 15 Sep 2025 23:47:10 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/introducing-traversablevalibot-j3d</link>
      <guid>https://dev.to/ahrjarrett/introducing-traversablevalibot-j3d</guid>
      <description>&lt;p&gt;A few weeks ago I released a TypeScript library called &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post covers what the library does, and highlights a few of its unique features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cover photo credit:&lt;/strong&gt; &lt;br&gt;
&lt;a href="https://www.1101.com/store/techo/en/2021/pc/detail_cover/oc21_atom/" rel="noopener noreferrer"&gt;&lt;em&gt;Astro Boy Schematics (鉄腕アトム)&lt;/em&gt;&lt;/a&gt;, Tezuka Productions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;br&gt;
Currently &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/strong&gt; only works with the latest version of Valibot (v1.1).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What makes &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/strong&gt; different?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/strong&gt; can be used in 2 ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick one of &lt;u&gt;&lt;strong&gt;over 15 transformers&lt;/strong&gt;&lt;/u&gt; available off-the-shelf&lt;/li&gt;
&lt;li&gt;
&lt;u&gt;&lt;strong&gt;Hand-roll your own&lt;/strong&gt;&lt;/u&gt; custom transformer&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Off-the-shelf
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxcheck" rel="noopener noreferrer"&gt;&lt;code&gt;vx.check&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Turn a Valibot schema into a super-performant type-guard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#performance-comparison-2" rel="noopener noreferrer"&gt;Performance profile&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxdeepclone" rel="noopener noreferrer"&gt;&lt;code&gt;vx.deepClone&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Valibot schema into a “deep copy” function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-clone-function-5fe0"&gt;How I built JavaScript's fastest deep clone function&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxdeepequal" rel="noopener noreferrer"&gt;&lt;code&gt;vx.deepEqual&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Valibot schema into a “deep equal” function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-equals-function-51n8"&gt;How I built JavaScript's fastest deep equals function&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxtostring" rel="noopener noreferrer"&gt;&lt;code&gt;vx.toString&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Valibot schema into a string&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Useful for testing, codegen, sanity checking&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxtotype" rel="noopener noreferrer"&gt;&lt;code&gt;vx.toType&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Valibot schema into a TypeScript type&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏷️ Preserves schema metadata as JSDoc annotations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxdefaultValue" rel="noopener noreferrer"&gt;&lt;code&gt;vx.defaultValue&lt;/code&gt;&lt;/a&gt; ✨&lt;em&gt;&lt;strong&gt;new!&lt;/strong&gt;&lt;/em&gt;✨
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Derive a configurable default value from a schema&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Designed for schema-driven forms on the frontend&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;&lt;code&gt;vx.makeLens&lt;/code&gt;&lt;/a&gt; ✨&lt;em&gt;&lt;strong&gt;coming soon!&lt;/strong&gt;&lt;/em&gt;✨
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🔍 Focus nested values with &lt;strong&gt;lenses&lt;/strong&gt; (getters/setters)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🌈 Reify control flow with &lt;strong&gt;prisms&lt;/strong&gt; (pattern-matchers)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⛰️ Map over containers with &lt;strong&gt;traversals&lt;/strong&gt; (for-loops)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧱 Stack them together with &lt;strong&gt;function composition&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Roll your own
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ᯓ🚀 &lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxfold" rel="noopener noreferrer"&gt;&lt;code&gt;vx.fold&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;All the off-the-shelf transformers that &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/strong&gt; ships are powered by &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot#vxfold" rel="noopener noreferrer"&gt;vx.fold&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Under the hood, the library uses an abstraction called &lt;a href="https://www.tweag.io/blog/2025-04-10-rust-recursion-schemes/" rel="noopener noreferrer"&gt;recursion schemes&lt;/a&gt; that make implementing recursion &lt;em&gt;simple&lt;/em&gt; and &lt;em&gt;fun&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To demonstrate, I used it to implement a &lt;u&gt;&lt;strong&gt;mock data generator&lt;/strong&gt;&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;The implementation is so minimal (25 lines!) that I've embedded it here, in its entirety:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/traversable-valibot-demo?embed=1&amp;amp;file=src%2FvalibotToFaker.ts&amp;amp;hideExplorer=1&amp;amp;terminalHeight=0&amp;amp;theme=light&amp;amp;initialpath=__vitest__/&amp;amp;view=editor" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To keep things simple, this is a partial implementation.&lt;/p&gt;

&lt;p&gt;For a more robust version, see &lt;strong&gt;&lt;a href="https://stackblitz.com/edit/traversable-valibot-faker-example?file=test%2Ffake.test.ts,src%2Ffake.ts&amp;amp;initialPath=__vitest__/" rel="noopener noreferrer"&gt;this StackBlitz&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Bonus: &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot-test" rel="noopener noreferrer"&gt;@traversable/valibot-test&lt;/a&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot-test" rel="noopener noreferrer"&gt;@traversable/valibot-test&lt;/a&gt;&lt;/strong&gt; is a testing library built specifically for library authors who are building on top of Valibot.&lt;/p&gt;

&lt;p&gt;Under the hood, it uses a TypeScript library called &lt;a href="https://github.com/dubzzz/fast-check" rel="noopener noreferrer"&gt;fast-check&lt;/a&gt; to &lt;u&gt;&lt;strong&gt;generate random Valibot schemas&lt;/strong&gt;&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;Being able to generate random schemas turns out to be incredibly useful for &lt;u&gt;&lt;strong&gt;fuzz testing&lt;/strong&gt;&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;For example, here we use it to test that our &lt;code&gt;valibotToFaker&lt;/code&gt; function always generates valid data:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/traversable-valibot-demo?embed=1&amp;amp;file=test%2FvalibotToFaker.test.ts&amp;amp;hideExplorer=1&amp;amp;theme=light&amp;amp;initialpath=__vitest__/&amp;amp;view=editor" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Tip:&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Scroll up in the terminal panel&lt;/u&gt; to see the generated schemas + mock data!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6djiouvlidklc1f0tni.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6djiouvlidklc1f0tni.gif" alt="@traversable/valibot-test demo"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Updates
&lt;/h2&gt;

&lt;p&gt;Update: as of yesterday, &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/strong&gt; is part of the Valibot ecosystem!&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/open-circle/valibot/pull/1298" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        adds @traversable/valibot and @traversable/valibot-test to ecosystem
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#1298&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/ahrjarrett" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F15133992%3Fv%3D4" alt="ahrjarrett avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/ahrjarrett" rel="noopener noreferrer"&gt;ahrjarrett&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/open-circle/valibot/pull/1298" rel="noopener noreferrer"&gt;&lt;time&gt;Sep 07, 2025&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Hello Valibot maintainers!&lt;/p&gt;
&lt;p&gt;This PR adds 2 libraries to the ecosystem:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot-test" rel="noopener noreferrer"&gt;@traversable/valibot-test&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These libraries were primarily built for developers who are building tools on top of Valibot, but can also be used for one of the 10+ off-the-shelf transformers that we currently support.&lt;/p&gt;
&lt;p&gt;Happy to talk more about the library if you have any questions!&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/open-circle/valibot/pull/1298" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/open-circle/valibot/pull/1298" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        adds @traversable/valibot and @traversable/valibot-test to ecosystem
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#1298&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/ahrjarrett" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F15133992%3Fv%3D4" alt="ahrjarrett avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/ahrjarrett" rel="noopener noreferrer"&gt;ahrjarrett&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/open-circle/valibot/pull/1298" rel="noopener noreferrer"&gt;&lt;time&gt;Sep 07, 2025&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Hello Valibot maintainers!&lt;/p&gt;
&lt;p&gt;This PR adds 2 libraries to the ecosystem:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot-test" rel="noopener noreferrer"&gt;@traversable/valibot-test&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These libraries were primarily built for developers who are building tools on top of Valibot, but can also be used for one of the 10+ off-the-shelf transformers that we currently support.&lt;/p&gt;
&lt;p&gt;Happy to talk more about the library if you have any questions!&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/open-circle/valibot/pull/1298" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The Valibot ecosystem is by far the most &lt;u&gt;welcoming&lt;/u&gt; and &lt;u&gt;collaborative&lt;/u&gt; TypeScript schema community out there, so I'm super proud to be able to contribute back ☺️.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;If you have any feedback, feature requests, or questions, feel free to &lt;a href="https://github.com/traversable/schema/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; or &lt;a href="https://github.com/traversable/schema/discussions" rel="noopener noreferrer"&gt;start a discussion&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;@traversable/valibot&lt;/a&gt;&lt;/strong&gt; on GitHub&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stackblitz.com/edit/traversable-valibot-demo?file=test%2Fdemo.test.ts,src%2Fdemo.ts,README.md&amp;amp;initialPath=__vitest__/" rel="noopener noreferrer"&gt;&lt;strong&gt;valibotToFaker&lt;/strong&gt; demo&lt;/a&gt; on StackBlitz&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stackblitz.com/edit/traversable-valibot-faker-example?file=test%2Ffake.test.ts,src%2Ffake.ts&amp;amp;initialPath=__vitest__/" rel="noopener noreferrer"&gt;Fully-fledged &lt;strong&gt;valibotToFaker&lt;/strong&gt; demo&lt;/a&gt; on StackBlitz&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introducing: @traversable/zod</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Tue, 02 Sep 2025 13:10:19 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/introducing-traversablezod-5dd4</link>
      <guid>https://dev.to/ahrjarrett/introducing-traversablezod-5dd4</guid>
      <description>&lt;p&gt;A few weeks ago I released a TypeScript library called &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;@traversable/zod&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post covers what the library does, and highlights a few of its unique features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;br&gt;
Currently &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;@traversable/zod&lt;/a&gt;&lt;/strong&gt; only works with the latest version of Zod (v4).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cover photo credit:&lt;/strong&gt; &lt;br&gt;
Jack Noble, &lt;a href="https://steamcommunity.com/sharedfiles/filedetails/?id=2203295335" rel="noopener noreferrer"&gt;&lt;em&gt;Mob Psycho 100 モブサイコ100&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What makes &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;@traversable/zod&lt;/a&gt;&lt;/strong&gt; different?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;@traversable/zod&lt;/a&gt;&lt;/strong&gt; can be used in 2 ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick one of &lt;u&gt;&lt;strong&gt;over 25 transformers&lt;/strong&gt;&lt;/u&gt; available off-the-shelf&lt;/li&gt;
&lt;li&gt;
&lt;u&gt;&lt;strong&gt;Hand-roll your own&lt;/strong&gt;&lt;/u&gt; custom transformer&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Off-the-shelf
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxcheck" rel="noopener noreferrer"&gt;&lt;code&gt;zx.check&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Zod schema into a super-performant type-guard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#performance-comparison-2" rel="noopener noreferrer"&gt;Performance profile&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxdeepclone" rel="noopener noreferrer"&gt;&lt;code&gt;zx.deepClone&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Zod schema into a "deep copy" function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-clone-function-5fe0"&gt;How I built JavaScript's fastest deep clone function&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxdeepequal" rel="noopener noreferrer"&gt;&lt;code&gt;zx.deepEqual&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Zod schema into a "deep equal" function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-equals-function-51n8"&gt;How I built JavaScript's fastest deep equals function&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxdeeppartial" rel="noopener noreferrer"&gt;&lt;code&gt;zx.deepPartial&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Makes all the properties of a Zod schema optional&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://zod.dev/v4/changelog?id=drops-deeppartial" rel="noopener noreferrer"&gt;Zod drops &lt;code&gt;deepPartial&lt;/code&gt; in v4&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxtostring" rel="noopener noreferrer"&gt;&lt;code&gt;zx.toString&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Zod schema into a string&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Useful for testing, codegen, sanity checking&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxtotype" rel="noopener noreferrer"&gt;&lt;code&gt;zx.toType&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts a Zod schema into a TypeScript type&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preserves schema metadata as JSDoc annotations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxmakelens" rel="noopener noreferrer"&gt;&lt;code&gt;zx.makeLens&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🔍 Focus nested values with &lt;strong&gt;lenses&lt;/strong&gt; (getters/setters)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🌈 Reify control flow with &lt;strong&gt;prisms&lt;/strong&gt; (pattern-matchers)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⛰️ Map over containers with &lt;strong&gt;traversals&lt;/strong&gt; (for-loops)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧱 Stack them together with &lt;strong&gt;function composition&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Roll your own
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ᯓ📦 &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxfold" rel="noopener noreferrer"&gt;&lt;code&gt;zx.fold&lt;/code&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;All the off-the-shelf transformers that &lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;@traversable/zod&lt;/a&gt;&lt;/strong&gt; ships are powered by &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxfold" rel="noopener noreferrer"&gt;&lt;code&gt;zx.fold&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Under the hood, the library uses an abstraction called &lt;a href="https://www.tweag.io/blog/2025-04-10-rust-recursion-schemes/" rel="noopener noreferrer"&gt;recursion schemes&lt;/a&gt; that make implementing recursion &lt;em&gt;simple&lt;/em&gt; and &lt;em&gt;fun&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To demonstrate, I used it to implement &lt;strong&gt;&lt;code&gt;toJsonSchema&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The implementation is so minimal (28 lines!) that I've embedded it here, in its entirety:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/traversable-zod?embed=1&amp;amp;file=src%2FtoJsonSchema.ts&amp;amp;hideExplorer=1&amp;amp;terminalHeight=0&amp;amp;theme=light&amp;amp;initialpath=__vitest__/&amp;amp;view=editor" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I specifically chose &lt;strong&gt;&lt;code&gt;toJsonSchema&lt;/code&gt;&lt;/strong&gt; because multiple libraries already exist that do this, including Zod itself.&lt;/p&gt;

&lt;p&gt;The goal is to showcase how you can use &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod#zxfold" rel="noopener noreferrer"&gt;&lt;code&gt;zx.fold&lt;/code&gt;&lt;/a&gt; to build a library of your own.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;If you have any feedback, feature requests, or questions, feel free to &lt;a href="https://github.com/traversable/schema/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; or &lt;a href="https://github.com/traversable/schema/discussions" rel="noopener noreferrer"&gt;start a discussion&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;@traversable/zod&lt;/a&gt;&lt;/strong&gt; on GitHub&lt;/li&gt;
&lt;li&gt;The full &lt;strong&gt;&lt;a href="https://stackblitz.com/edit/traversable-zod?file=test%2Ffuzz.test.ts,src%2FtoJsonSchema.ts&amp;amp;initialPath=__vitest__/" rel="noopener noreferrer"&gt;&lt;code&gt;toJsonSchema&lt;/code&gt; demo&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Is your code optimized for V8's JIT compiler?</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Tue, 05 Aug 2025 10:54:16 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/is-your-code-optimized-for-v8s-jit-compiler-195n</link>
      <guid>https://dev.to/ahrjarrett/is-your-code-optimized-for-v8s-jit-compiler-195n</guid>
      <description>&lt;p&gt;Ever wondered if your code is optimized for V8's just-in-time compiler?&lt;/p&gt;

&lt;p&gt;Here's a neat trick: use the Chrome DevTools. If V8 can "see" the structure ahead of time, that's a signal that you're on the right track.&lt;/p&gt;

&lt;p&gt;Probably the easiest way to de-optimize your code is to use recursion. Recursion does not preserve structure.&lt;/p&gt;

&lt;p&gt;And if you think about it, that makes sense. How could it? We'd need to solve the halting problem first.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to build JavaScript's fastest “deep clone” function</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Wed, 30 Jul 2025 01:34:07 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-clone-function-5fe0</link>
      <guid>https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-clone-function-5fe0</guid>
      <description>&lt;p&gt;This is a longer-form article about how I built JavaScript's fastest “deep clone” function.&lt;/p&gt;

&lt;p&gt;Last time when I talked about &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-equals-function-51n8"&gt;implementing “deep equal”&lt;/a&gt;, I ended by basically telling readers to &lt;a href="https://substackcdn.com/image/fetch/$s_!6BY7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F2cfec30a-9684-4445-8a02-17300b7e40e1_1153x987.png" rel="noopener noreferrer"&gt;draw the rest of the owl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This time let's get in the weeds.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you haven't already, read through the post on &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-equals-function-51n8"&gt;implementing JavaScript's fastest “deep equal”&lt;/a&gt;. It's a short read (~5 minutes), and we'll be picking up where we left off in this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  tl;dr
&lt;/h3&gt;

&lt;p&gt;The implementation we'll be talking about today is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10-50x faster&lt;/strong&gt; than &lt;code&gt;Lodash.deepClone&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20-120x faster&lt;/strong&gt; than &lt;code&gt;window.structuredClone&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're skeptical, you can &lt;a href="https://bolt.new/~/mitata-4mmmdyre" rel="noopener noreferrer"&gt;run the benchmarks yourself on Bolt&lt;/a&gt; by typing &lt;code&gt;npm run bench&lt;/code&gt; in the terminal.&lt;/p&gt;

&lt;p&gt;Before we get into the &lt;em&gt;how&lt;/em&gt;, let's first make sure we're on the same page about the problem we're trying to solve.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;u&gt;Disclaimer&lt;/u&gt;:&lt;/p&gt;

&lt;p&gt;You might not need a deep copy.&lt;/p&gt;

&lt;p&gt;Even if cloning were “free” (instantaneous), you still pay for the memory allocation.&lt;/p&gt;

&lt;p&gt;Other solutions, such as structural sharing, can help you balance that cost, and could very well be a better fit for your use case.&lt;/p&gt;

&lt;p&gt;As always, make sure you're aware of the tradeoffs, and think critically before you go around minting new copies of your data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's get into it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Managing shared references to mutable data is hard. &lt;/p&gt;

&lt;p&gt;And JavaScript offers little help here: the syntax for mutating a value (&lt;code&gt;=&lt;/code&gt;) is identical to the syntax for binding a variable (&lt;code&gt;=&lt;/code&gt;), and similar to the syntax for comparing two values (&lt;code&gt;==&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If you're an experienced JavaScript developer, you've probably learned to be hyper-vigilant about preventing accidental mutation.&lt;/p&gt;

&lt;p&gt;Sometimes, though, you need a guarantee. Sometimes you need to make a copy.&lt;/p&gt;

&lt;h3&gt;
  
  
  So what's the problem?
&lt;/h3&gt;

&lt;p&gt;Copying data is slow.&lt;/p&gt;

&lt;p&gt;Part of the problem is that allocating memory takes time. This part is, to some extent, unavoidable.&lt;/p&gt;

&lt;p&gt;Part of the problem is that the &lt;em&gt;algorithm&lt;/em&gt; is slow. This is the part we can change.&lt;/p&gt;




&lt;h2&gt;
  
  
  A “naive” solution
&lt;/h2&gt;

&lt;p&gt;How can we make deep cloning fast?&lt;/p&gt;

&lt;p&gt;As usual we start with a naive solution.&lt;/p&gt;

&lt;p&gt;Let's say we're working with the following data model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what a user looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sherlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sherlock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Holmes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221 Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Apartment B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's practicing our “unlearning” skills and hardcode the solution, like we did last time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deepClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's benchmark it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmarking our naive solution
&lt;/h2&gt;

&lt;p&gt;If you'd like to run the benchmarks yourself, you can do so in the &lt;a href="https://bolt.new/~/mitata-6zskc9zd" rel="noopener noreferrer"&gt;Bolt sandbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's the breakdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lodash                       205.09 µs/iter 203.25 µs  █
                    (173.25 µs … 883.04 µs) 829.71 µs ▄█
                    ( 14.52 kb … 725.08 kb)  65.87 kb ██▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
                  4.49 ipc (  0.66% stalls)  99.07% L1 data cache
        621.43k cycles   2.79M instructions  33.01% retired LD/ST (920.05k)

deepClone                      5.40 µs/iter   5.45 µs            ▂█
                        (5.19 µs … 5.65 µs)   5.61 µs          ▃███
                    (  0.83  b …   1.97 kb) 169.67  b █▃▇█▃▃▁▃▁████▇▇▅▁▃▅▃▃
                  5.68 ipc (  1.07% stalls)  99.62% L1 data cache
         17.05k cycles  96.82k instructions  37.40% retired LD/ST ( 36.21k)

          ┌                                            ┐
Lodash    ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 205.09 µs
deepClone ┤ 5.40 µs
          └                                            ┘

          ┌                                            ┐
                   ╷┌┬                                 ╷
   Lodash          ├┤│─────────────────────────────────┤
                   ╵└┴                                 ╵
           ┬
deepClone  │
           ┴
          └                                            ┘
          5.19 µs          417.45 µs           829.71 µs

summary
  deepClone
   37.98x faster than Lodash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yep, it's fast. It could probably be faster, but this blogpost isn't about micro-optimization – it's about drawing the rest of the owl.&lt;/p&gt;

&lt;p&gt;Two call outs, before we move on:&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency
&lt;/h3&gt;

&lt;p&gt;Notice how &lt;em&gt;inconsistent&lt;/em&gt; Lodash is. Here's the range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;173.25 µs … 883.04 µs

883.04 / 173.25 = 5.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a lot of variance. Depending on the run, Lodash sometimes took &lt;strong&gt;400% more time&lt;/strong&gt; to do the job.&lt;/p&gt;

&lt;p&gt;Compare that to our implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(5.19 µs … 5.65 µs)

5.65 / 5.19 = 1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By comparison, &lt;strong&gt;our implementation deviated by only 10%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So in addition to being faster, it's also more predictable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That makes sense – ours knows the shape of the data it will receive, so its performance is similarly knowable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Instruction count
&lt;/h3&gt;

&lt;p&gt;Notice how much work Lodash is doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;621.43k cycles   2.79M instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;17.05k cycles  96.82k instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That also makes sense. Our implementation doesn't need to introspect the data, or make any decisions. All it does is clone data.&lt;/p&gt;

&lt;p&gt;Interestingly, the difference in instruction count differs by nearly the same factor as time spent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lodash:                2.79M instructions
Ours:   96_820 * 37x = 3.58M instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Implementing JavaScript's fastest “deep clone”
&lt;/h2&gt;

&lt;p&gt;Like last time, we'll use a “spec” (JSON Schema) to front-load the cost, so we only have to pay it once.&lt;/p&gt;

&lt;p&gt;And, like last time, we'll use the schema to build up our program &lt;em&gt;as a string&lt;/em&gt;, which we can then pass to the built-in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function" rel="noopener noreferrer"&gt;&lt;code&gt;Function&lt;/code&gt; constructor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you haven't seen this pattern before, my &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-equals-function-51n8"&gt;previous post&lt;/a&gt; talks about the pattern and why it works.&lt;/p&gt;

&lt;p&gt;In other words, this is what we want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deepClone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`
  return {
    firstName: data.firstName,
    ...data.lastName !== undefined &amp;amp;&amp;amp; 
    ({ lastName: data.lastName }),
    addresses: data.addresses.map((value) =&amp;gt; ({
      street1: value.street1,
      ...value.street2 !== undefined &amp;amp;&amp;amp;
      ({ street2: value.street2 }),
      city: value.city
    }))
  }
`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mapping schema to syntax
&lt;/h3&gt;

&lt;p&gt;Our implementation will take a JSON Schema as input, and return a big string.&lt;/p&gt;

&lt;p&gt;We'll have to use recursion at some point, but postpone that part for as long as we can.&lt;/p&gt;

&lt;p&gt;First, we need to decompose our big problem into smaller sub-problems.&lt;/p&gt;

&lt;p&gt;When it comes to recursion, it's almost always easier to start at the leaf nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaf nodes
&lt;/h3&gt;

&lt;p&gt;How should we handle strings nodes?&lt;/p&gt;

&lt;p&gt;Let's peek at our implementation again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deepClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;
    &lt;span class="c1"&gt;//         𐙘____________𐙘&lt;/span&gt;
    &lt;span class="c1"&gt;//              here&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we're at a string node, we return the value at that address, in this case, the string &lt;code&gt;data.firstName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which brings us to our first problem: how do we get the path? How do we know to return &lt;code&gt;data.firstName&lt;/code&gt;, and not &lt;code&gt;data.lastName&lt;/code&gt;, or &lt;code&gt;data.x.y.z&lt;/code&gt; for that matter?&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuation passing style (CPS)
&lt;/h3&gt;

&lt;p&gt;If you're like me, your first thought might have been to create a local “path” variable, or add a “context” parameter, where we keep track of the path to our data.&lt;/p&gt;

&lt;p&gt;That would work, but on the other hand, that sounds tricky. Implementing that will require a lot of “top-level” coordination, which (speaking from experience) is brittle and tricky to debug (if we screw up the coordination, we'll need to start at the top, and work our way down).&lt;/p&gt;

&lt;p&gt;Alternatively, we could punt on all that, and make our implementation a function that accepts the path as an argument. That puts the problem of "building up a path" much closer to where we'll actually be using it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;joinPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloneString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;joinPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's actually all we need to do for the leaf nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Array nodes
&lt;/h3&gt;

&lt;p&gt;Okay, let's do arrays.&lt;/p&gt;

&lt;p&gt;For arrays, our implementation will need to return a function that accepts a path, and returns a string.&lt;/p&gt;

&lt;p&gt;In JSON Schema, arrays are represented like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in our case, since we're working from the bottom up, &lt;code&gt;{ type: 'string' }&lt;/code&gt; will be a function, instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;
  &lt;span class="c1"&gt;//     𐙘_____𐙘&lt;/span&gt;
  &lt;span class="c1"&gt;//         we already replaced the string node&lt;/span&gt;
  &lt;span class="c1"&gt;//         with `cloneString`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we'll need to call it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloneArray&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;joinPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
    &lt;span class="s2"&gt;`.map((value) =&amp;gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])}&lt;/span&gt;&lt;span class="s2"&gt;))`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, our array cloner will be called with its child node (&lt;code&gt;builder&lt;/code&gt;) as an argument, then, since it's a &lt;code&gt;Builder&lt;/code&gt;, it receives the path from its parent.&lt;/p&gt;

&lt;p&gt;It then passes a &lt;strong&gt;new path&lt;/strong&gt; (the string &lt;code&gt;"value"&lt;/code&gt;) down to its child, since its children don't need to care about the full path from the root anymore.&lt;/p&gt;

&lt;p&gt;If you're still confused, it might help to look at our original implementation again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nl"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;//                           𐙘___𐙘&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's actually all we need to do for arrays.&lt;/p&gt;

&lt;h3&gt;
  
  
  Object nodes
&lt;/h3&gt;

&lt;p&gt;Objects will be a little trickier, but not by much.&lt;/p&gt;

&lt;p&gt;Here's the JSON Schema for object schemas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we're building from the bottom-up, here's what our objects schemas will look like, mid-traversal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, instead of receiving a single builder as a child, we'll be receiving a record whose values are each builders.&lt;/p&gt;

&lt;p&gt;We'll also need to call each of the builders with its key appended to the end of the path. Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloneObject&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
          &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; 
          &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
          &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; }&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it for objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiring things up
&lt;/h3&gt;

&lt;p&gt;Let's wire up our individual schema handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;JsonSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fold&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsonSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cloneString&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;cloneArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;cloneObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that our &lt;code&gt;JsonSchema&lt;/code&gt; type &lt;strong&gt;isn't recursive&lt;/strong&gt;. Not only does this help with TypeScript IDE performance, but it also lets us accurately represent the &lt;em&gt;actual type&lt;/em&gt; of our schema &lt;em&gt;during traversal&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Note that our &lt;code&gt;fold&lt;/code&gt; function &lt;strong&gt;is also not recursive&lt;/strong&gt;. We're not recursing yet; we're still wiring things up.&lt;/p&gt;

&lt;p&gt;If you haven't seen this pattern before, I wrote about it previously in &lt;a href="https://dev.to/ahrjarrett/typesafe-recursion-in-typescript-1pe0"&gt;Recursion in TypeScript (without the tears)&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making it recursive
&lt;/h2&gt;

&lt;p&gt;I said this already, but it's worth reiterating: this section assumes you're familiar with the idea of a Functor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;If you need to brush up, here are a few resources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html" rel="noopener noreferrer"&gt;Functors, applicatives and Monads in pictures&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/ahrjarrett/typesafe-recursion-in-typescript-1pe0"&gt;Recursion in TypeScript&lt;/a&gt;, which covers the same pattern in more depth&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Okay, we're finally ready to wire up our &lt;code&gt;fold&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;To do that, we need 2 more components:&lt;/p&gt;

&lt;h3&gt;
  
  
  A JSON Schema &lt;a href="https://www.adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html" rel="noopener noreferrer"&gt;Functor&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A Functor is an “interface” from functional programming.&lt;/p&gt;

&lt;p&gt;Data that satisfies the Functor interface has a single method, &lt;code&gt;map&lt;/code&gt;, which takes a function, and applies the function to the element(s) inside the Functor.&lt;/p&gt;

&lt;p&gt;If you've ever used &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" rel="noopener noreferrer"&gt;&lt;code&gt;Array.prototype.map&lt;/code&gt;&lt;/a&gt; before, then you're already familiar with the Array Functor.&lt;/p&gt;

&lt;p&gt;Let's define a Functor for JSON Schema specs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Functor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsonSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;JsonSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A &lt;code&gt;combine&lt;/code&gt; function
&lt;/h3&gt;

&lt;p&gt;To do that, we're going to write a function called &lt;code&gt;combine&lt;/code&gt;. I call it that because it's closely related to the &lt;a href="https://en.wikipedia.org/wiki/Fixed-point_combinator#Y_combinator" rel="noopener noreferrer"&gt;Y-combinator&lt;/a&gt;, and because the traditional name for it (&lt;a href="https://maartenfokkinga.github.io/utwente/mmf91m.pdf" rel="noopener noreferrer"&gt;catamorphism&lt;/a&gt;) isn't particularly accessible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Fold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsonSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combine&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Fold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Fold&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Functor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Let's put it all together now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildDeepClone&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsonSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;return &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For ~60 lines of code (about 20 of them types), that's not bad, especially for a solution that's an order of magnitude faster than the one that ships with the platform.&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href="https://stackblitz.com/edit/partial-deep-clone?file=src%2FdeepClone.ts" rel="noopener noreferrer"&gt;sandbox&lt;/a&gt; if you'd like to see it working.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fuzz testing
&lt;/h2&gt;

&lt;p&gt;We're not done yet. We've tested our &lt;code&gt;buildDeepClone&lt;/code&gt; function with a few hardcoded inputs, but that doesn't give us enough confidence to ship this as a library.&lt;/p&gt;

&lt;p&gt;I don't know about you, but I'm lazy. I'd rather not rack my brain to think of edge cases, and my fingers hurt just thinking about typing them all out.&lt;/p&gt;

&lt;p&gt;Not to mention, those kinds of tests are super brittle: if we change something about our implementation, we have to go and update a bunch of tests by hand, which often involves a bunch of guess-work to figure out what the tests &lt;em&gt;mean&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's fuzz test our &lt;code&gt;buildDeepClone&lt;/code&gt; function instead.&lt;/p&gt;

&lt;p&gt;For that, we'll use a library called &lt;a href="https://github.com/dubzzz/fast-check" rel="noopener noreferrer"&gt;&lt;code&gt;fast-check&lt;/code&gt;&lt;/a&gt;. If you're not familiar with it, now you know.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;fast-check&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;fast-check&lt;/code&gt; is a &lt;a href="https://en.wikipedia.org/wiki/Property_testing" rel="noopener noreferrer"&gt;property testing&lt;/a&gt; library that exposes a &lt;a href="https://en.wikipedia.org/wiki/Combinatory_logic" rel="noopener noreferrer"&gt;combinator&lt;/a&gt; API that will feel very familiar if you've used &lt;code&gt;zod&lt;/code&gt; before.&lt;/p&gt;

&lt;p&gt;You use these combinators to build up &lt;em&gt;arbitraries&lt;/em&gt;. &lt;em&gt;Arbitraries&lt;/em&gt; are pseudo-random data generators.&lt;/p&gt;

&lt;p&gt;Our goal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;generate a random JSON Schema&lt;/li&gt;
&lt;li&gt;generate a random input that satisfies the JSON Schema&lt;/li&gt;
&lt;li&gt;pass the schema to our &lt;code&gt;buildCloneDeep&lt;/code&gt; function to derive a &lt;code&gt;deepClone&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;pass the random input to our &lt;code&gt;deepClone&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;vitest&lt;/code&gt; to make sure that the generated input and the cloned input are deeply equal to each other&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Generate a random JSON Schema
&lt;/h3&gt;

&lt;p&gt;First, let's write an arbitrary that generates a “random” (pseudo-random) string schema.&lt;/p&gt;

&lt;h4&gt;
  
  
  Generating a string schema
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stringSchemaArbitrary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example we're using the &lt;code&gt;fc.constant&lt;/code&gt; combinator to generate a constant value.&lt;/p&gt;

&lt;p&gt;The reason we need to wrap this constant value in an arbitrary is so that we can compose it with other the other arbitraries we need to write.&lt;/p&gt;

&lt;h4&gt;
  
  
  Generating an array schema
&lt;/h4&gt;

&lt;p&gt;Okay, let's build the array schema arbitrary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arraySchemaArbitrary&lt;/span&gt; 
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Arbitrary&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Generating an object schema
&lt;/h4&gt;

&lt;p&gt;Now let's build the object schema arbitrary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objectSchemaArbitrary&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Arbitrary&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  All together now
&lt;/h4&gt;

&lt;p&gt;Okay, so we have our component parts. How do we compose them to generate a random JSON Schema?&lt;/p&gt;

&lt;p&gt;For that, we'll use &lt;a href="https://fast-check.dev/docs/core-blocks/arbitraries/combiners/recursive-structure/#letrec" rel="noopener noreferrer"&gt;&lt;code&gt;fc.letrec&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;fc.letrec&lt;/code&gt; API might feel familiar to you. That's because it's design is very similar to the “continuation passing” approach we used earlier to generate our stringified function bodies.&lt;/p&gt;

&lt;p&gt;Here's what using it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SchemaBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;letrec&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stringSchemaArbitrary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;arraySchemaArbitrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;objectSchemaArbitrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;oneof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;tie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;tie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;tie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;twoSchemas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SchemaBuilder&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twoSchemas&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; {&lt;/span&gt;
&lt;span class="c1"&gt;//   "type": "object",&lt;/span&gt;
&lt;span class="c1"&gt;//   "properties": {&lt;/span&gt;
&lt;span class="c1"&gt;//     "$C_5g$G$": { "type": "string" },&lt;/span&gt;
&lt;span class="c1"&gt;//       "G_If_1$_": { "type": "string" },&lt;/span&gt;
&lt;span class="c1"&gt;//       "ku": { "type": "string" },&lt;/span&gt;
&lt;span class="c1"&gt;//       "$_$_9_c$I": {&lt;/span&gt;
&lt;span class="c1"&gt;//         "type": "array",&lt;/span&gt;
&lt;span class="c1"&gt;//         "items": { "type": "string" }&lt;/span&gt;
&lt;span class="c1"&gt;//       },&lt;/span&gt;
&lt;span class="c1"&gt;//       "VF$_$": { "type": "string" },&lt;/span&gt;
&lt;span class="c1"&gt;//       "G": { "type": "string" }&lt;/span&gt;
&lt;span class="c1"&gt;//     }&lt;/span&gt;
&lt;span class="c1"&gt;//   }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twoSchemas&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; {&lt;/span&gt;
&lt;span class="c1"&gt;//   "type": "array",&lt;/span&gt;
&lt;span class="c1"&gt;//   "items": {&lt;/span&gt;
&lt;span class="c1"&gt;//     "type": "array",&lt;/span&gt;
&lt;span class="c1"&gt;//     "items": { "type": "string" }&lt;/span&gt;
&lt;span class="c1"&gt;//   }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That covers our random JSON Schema generator. What about the inputs?&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the schema to generate random inputs
&lt;/h3&gt;

&lt;p&gt;For this, we can re-use the &lt;code&gt;combine&lt;/code&gt; function we wrote earlier to avoid having to deal with walking the schema tree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's unpack that:&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;generateData&lt;/code&gt; function accepts a JSON Schema as input, then pattern matches on the schema's &lt;code&gt;"type"&lt;/code&gt; property:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;code&gt;schema.type === "string"&lt;/code&gt;, we return a string arbitrary&lt;/li&gt;
&lt;li&gt;if &lt;code&gt;schema.type === "array"&lt;/code&gt;, we return an array arbitrary, passing the arbitrary at &lt;code&gt;schema.items&lt;/code&gt; into &lt;code&gt;fc.array&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;if &lt;code&gt;schema.type === "object"&lt;/code&gt;, we return a record arbitrary, passing the record of arbitraries at &lt;code&gt;schema.properties&lt;/code&gt; into &lt;code&gt;fc.record&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's it. Because &lt;code&gt;combine&lt;/code&gt; handles the recursion for us, we're done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing our test
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;fast-check&lt;/code&gt; is designed to integrate seamlessly with &lt;code&gt;vitest&lt;/code&gt; (and if &lt;a href="https://github.com/vitest-dev/vitest/discussions/2212" rel="noopener noreferrer"&gt;this issue&lt;/a&gt; gets traction, it might someday be integrated as a first-class part of the API 🤞).&lt;/p&gt;

&lt;p&gt;Here's all we need to do to fuzz test that our &lt;code&gt;buildDeepClone&lt;/code&gt; function works with any arbitrary JSON Schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fuzz test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SchemaBuilder&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generateData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deepClone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildDeepClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deepClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;numRuns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Notice that we're running the test 1,000 times. We can of course configure that – by default it runs the test 100 times.&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href="https://stackblitz.com/edit/testing-partial-deep-clone?file=test%2FdeepClone.fuzz.test.ts" rel="noopener noreferrer"&gt;sandbox&lt;/a&gt; if you'd like to run the test suite yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you run the test suite, you can scroll up in the terminal to see the generated schema, generated input, and cloned data for each run.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Hopefully this helped you see, if not the whole owl, enough that you could draw the rest if you wanted.&lt;/p&gt;

&lt;p&gt;If you'd prefer not to write this kind of thing yourself, take a look at &lt;a href="https://github.com/traversable/schema/" rel="noopener noreferrer"&gt;&lt;code&gt;@traversable/schema&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We support generating &lt;code&gt;deepClone&lt;/code&gt;, &lt;code&gt;deepEqual&lt;/code&gt; and more for the follow schema types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;zod (v4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/arktype" rel="noopener noreferrer"&gt;ArkType&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/typebox" rel="noopener noreferrer"&gt;TypeBox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/json-schema" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/tree/main/packages/valibot" rel="noopener noreferrer"&gt;Valibot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And thanks for reading! This one was pretty long. If you made it all the way through and have feedback, or just want to chat about anything we talked about, feel free to reach out via email at &lt;a href="mailto:ahrjarrett@gmail.com"&gt;ahrjarrett@gmail.com&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Aside
&lt;/h3&gt;

&lt;p&gt;This approach to building leaner, more localized alternatives to bulky (and often slow) APIs was inspired by, of all things, Dune.&lt;/p&gt;

&lt;p&gt;More specifically, it was a video about the &lt;a href="https://www.youtube.com/watch?v=uUetPQdYJb0" rel="noopener noreferrer"&gt;thematic role of architecture in the Dune series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This quote, which compares the Harkonnen's design to the Fremen's, is what got me thinking about it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The [Harkonnens], even though they're technically the most powerful and resourceful family, struggle to adapt to Arrakis' harsh conditions, and they rely mostly on imported resources.&lt;/p&gt;

&lt;p&gt;The Fremen on the other hand have been able to develop solutions that are &lt;em&gt;locally adapted approaches&lt;/em&gt; that are specifically designed for [their] climate.&lt;/p&gt;

&lt;p&gt;Instead of seeking universally applicable high tech solutions, maybe we need to be looking more for nuanced locally adapted approaches.&lt;/p&gt;

&lt;p&gt;– Dami Lee, &lt;a href="https://youtu.be/uUetPQdYJb0?si=xdaDdNKvza8WaZxd&amp;amp;t=984" rel="noopener noreferrer"&gt;Dune Cities: Fiction or Reality?&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think this kind of approach could be explored in our line of work more often. It seems like this is a relatively new frontier (for the JavaScript ecosystem, at least), and I'm excited to see what others come up with as we explore the space of possibilities.&lt;/p&gt;

&lt;p&gt;That's all for now. Cheers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bolt.new/~/mitata-4mmmdyre" rel="noopener noreferrer"&gt;Bolt sandbox&lt;/a&gt; where you can run the benchmarks&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/traversable/schema/tree/main/benchmarks" rel="noopener noreferrer"&gt;benchmarks&lt;/a&gt; themselves&lt;/li&gt;
&lt;li&gt;Examples of the &lt;a href="https://github.com/traversable/schema/blob/main/packages/json-schema/test/deep-clone.test.ts" rel="noopener noreferrer"&gt;generated code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;How the &lt;a href="https://github.com/traversable/schema/blob/main/packages/zod/test/deep-clone.bench.ts" rel="noopener noreferrer"&gt;benchmarks were conducted&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/traversable/schema/blob/main/packages/json-schema/src/deep-clone.ts" rel="noopener noreferrer"&gt;implementation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webperf</category>
    </item>
    <item>
      <title>How to build JavaScript's fastest “deep equals” function</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Sun, 13 Jul 2025 04:37:45 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-equals-function-51n8</link>
      <guid>https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-equals-function-51n8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the first article in a series. The follow-up is titled &lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-clone-function-5fe0"&gt;&lt;em&gt;How to build JavaScript's fastest “deep clone” function&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a short article about how I built JavaScript's fastest "deep equals" function.&lt;/p&gt;

&lt;h3&gt;
  
  
  tl;dr
&lt;/h3&gt;

&lt;p&gt;The implementation we'll be talking about today is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;35-65x faster&lt;/strong&gt; than NodeJS's built-in &lt;code&gt;isDeepStrictEqual&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50-110x faster&lt;/strong&gt; than &lt;code&gt;Lodash.isEqual&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're skeptical, you can run the benchmarks yourself on &lt;a href="https://bolt.new/~/mitata-ajbdjcot" rel="noopener noreferrer"&gt;Bolt&lt;/a&gt; by clicking "Code" and typing &lt;code&gt;node index.mjs&lt;/code&gt; in the terminal.&lt;/p&gt;

&lt;p&gt;Before we get into the &lt;em&gt;how&lt;/em&gt;, let's first make sure we're on the same page about the problem we're trying to solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we mean when we say 2 things are the same
&lt;/h2&gt;

&lt;p&gt;An &lt;a href="https://en.wikipedia.org/wiki/Equivalence_relation" rel="noopener noreferrer"&gt;equivalence relation&lt;/a&gt;, or "deep equals" in the parlance of the JS ecosystem, is a function that takes 2 arguments and returns a boolean indicating whether they are "the same".&lt;/p&gt;

&lt;p&gt;By "the same", typically we mean &lt;em&gt;equal by value&lt;/em&gt;, not &lt;em&gt;equal by reference&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;These are equal &lt;em&gt;by reference&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;abc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value1&lt;/span&gt;

&lt;span class="nx"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;value2&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are equal &lt;em&gt;by value&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;abc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;abc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;value2&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;value1 === value2&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; in the second example.&lt;/p&gt;

&lt;p&gt;The issue is that, given non-primitive operands, the &lt;code&gt;===&lt;/code&gt; operator computes an &lt;em&gt;identity&lt;/em&gt; rather than an &lt;em&gt;equivalence&lt;/em&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Put differently, the &lt;code&gt;===&lt;/code&gt; operator answers the question, "Do both pointers point to the same place in memory?" rather than "Are both values structurally the same?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why does it work that way?&lt;/p&gt;

&lt;h3&gt;
  
  
  The short answer
&lt;/h3&gt;

&lt;p&gt;The short answer is that &lt;a href="https://tc39.es/ecma262/multipage/notational-conventions.html#sec-identity" rel="noopener noreferrer"&gt;that's the spec&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The long answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Gather round the fire, let's talk about what we mean when we say two things are the same...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The long answer is that it's complicated. To see what I mean, try typing this out in your browser's DevTools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is part of a much larger conversation about &lt;em&gt;identity&lt;/em&gt; and &lt;em&gt;sameness&lt;/em&gt;. And while I think this is an interesting topic, it's been &lt;a href="https://people.math.osu.edu/cogdell.1/6112-Mazur-www.pdf" rel="noopener noreferrer"&gt;covered elsewhere&lt;/a&gt;, so let's sidestep it for now.&lt;/p&gt;




&lt;h2&gt;
  
  
  What do we actually want?
&lt;/h2&gt;

&lt;p&gt;What we actually want is a &lt;strong&gt;more relaxed definition of equality&lt;/strong&gt;. We want to know whether 2 values are equal &lt;em&gt;according to some criteria&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Let's say we're working with the following data model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How can we tell 2 addresses apart?&lt;/p&gt;

&lt;h4&gt;
  
  
  A "naive" solution
&lt;/h4&gt;

&lt;p&gt;Arguably the easiest solution is to hand-roll our own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addressEquals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street1&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street2&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;addressEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; true&lt;/span&gt;

&lt;span class="nf"&gt;addressEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4 Privet Dr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Little Whinging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Advantages
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;We control the implementation&lt;/li&gt;
&lt;li&gt;We have visibility into its behavior&lt;/li&gt;
&lt;li&gt;Great performance&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Disadvantages
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Tedious to write&lt;/li&gt;
&lt;li&gt;Easy to screw up&lt;/li&gt;
&lt;li&gt;Subject to rot: if we add a new property to our &lt;code&gt;Address&lt;/code&gt; type, it's easy to forget to update our equals function&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  A "heavy-handed" solution
&lt;/h4&gt;

&lt;p&gt;Another way to solve the problem is to use an off-the-shelf solution, like Lodash's &lt;code&gt;isEqual&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;isEqual&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lodash.isEqual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;isEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; true&lt;/span&gt;

&lt;span class="nf"&gt;isEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4 Privet Dr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Little Whinging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful in a pinch, but it isn't perfect. Namely, we've solved one problem by introducing a new one: &lt;code&gt;isEqual&lt;/code&gt; comes with a performance penalty, because it has to traverse both data structures recursively to compute the result.&lt;/p&gt;

&lt;p&gt;Do we have any other options?&lt;/p&gt;

&lt;h4&gt;
  
  
  Getting closer
&lt;/h4&gt;

&lt;p&gt;Another thing we could do is swap out our schema library for one that lets us derive an equals function &lt;em&gt;from&lt;/em&gt; the schema.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;effect&lt;/code&gt; library supports this. Let's see what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;effect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optionalWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// deriving an equals function from `Address`:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addressEquals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equivalence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;addressEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; true&lt;/span&gt;

&lt;span class="nf"&gt;addressEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4 Privet Dr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Little Whinging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Advantages
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Better performance than &lt;code&gt;isEqual&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implementations stay in sync&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Disadvantages
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;We have to switch our schema library, which may or may not be viable&lt;/li&gt;
&lt;li&gt;The performance is better, but only marginally so&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Comparison
&lt;/h3&gt;

&lt;p&gt;To recap, we have roughly 3 options, each with its own set of tradeoffs. We can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;"roll our own" — offers the best performance, but it's annoying and brittle&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;use an off-the-shelf solution — convenient, but comes with the worst performance profile&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;write our schemas using &lt;code&gt;effect&lt;/code&gt; — this solves #1 and partially alleviates #2, but requires us to migrate to &lt;code&gt;effect&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wouldn't it be &lt;strong&gt;great&lt;/strong&gt; if we had a way to somehow get the performance of hand-written equals functions, without having to actually write them by hand?&lt;/p&gt;

&lt;p&gt;Turns out, we can.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementing JavaScript's fastest “deep equals”
&lt;/h2&gt;

&lt;p&gt;Remember our "naive" solution? Here it is again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addressEquals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street1&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street2&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it turns out, our naive solution is also &lt;em&gt;super fast&lt;/em&gt;. In fact, it would be almost impossible to make it faster.&lt;/p&gt;

&lt;p&gt;Can we use our schema to generate the super fast implementation &lt;em&gt;as a string&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;If we could do that, we could write that implementation to disc, or use JavaScript's native &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function" rel="noopener noreferrer"&gt;&lt;code&gt;Function&lt;/code&gt; constructor&lt;/a&gt; to turn the string into code.&lt;/p&gt;

&lt;p&gt;That would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addressEquals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`
  if (x === y) return true
  if (x.street1 !== y.street1) return false
  if (x.street2 !== y.street2) return false
  if (x.city !== y.city) return false
  return true
`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've never seen this trick before, chances are you're probably using it already: if you use &lt;code&gt;zod@4&lt;/code&gt;, &lt;code&gt;arktype&lt;/code&gt; or &lt;code&gt;@sinclair/typebox&lt;/code&gt;, they all use the same trick under the hood when validating data.&lt;/p&gt;

&lt;p&gt;The performance increase can be dramatic — if this is your first time seeing this technique, &lt;a href="https://www.youtube.com/watch?v=yOJwB7Dcr5k" rel="noopener noreferrer"&gt;this video&lt;/a&gt; by the author of &lt;a href="https://github.com/modiimedia/arri" rel="noopener noreferrer"&gt;arri&lt;/a&gt; does a great job covering why it's fast, and how it works.&lt;/p&gt;

&lt;p&gt;So, we know what we want (to write a function that takes a zod schema, and builds up a string that will become our equals function) — but how would we go about doing that?&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing &lt;code&gt;@traversable/zod&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;That's exactly what &lt;code&gt;zx.deepEqual&lt;/code&gt; from  &lt;a href="https://github.com/traversable/schema/tree/main/packages/zod" rel="noopener noreferrer"&gt;&lt;code&gt;@traversable/zod&lt;/code&gt;&lt;/a&gt; does.&lt;/p&gt;

&lt;p&gt;Using it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@traversable/zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;street2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addressEquals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;addressEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; true&lt;/span&gt;

&lt;span class="nf"&gt;addressEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;221B Baker St&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;London&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4 Privet Dr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Little Whinging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You write your zod schemas as usual, and you get the fastest possible "deep equals" function for free.&lt;/p&gt;

&lt;p&gt;If you're curious how much faster, check out the benchmarks below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance comparison
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;zx.deepEqual&lt;/code&gt; performs better than every implementation I could find, in every category, on every run.&lt;/p&gt;

&lt;p&gt;I've included links at bottom in case you'd like to dig into what the generated code looks like, the benchmarks, or the implementation.&lt;/p&gt;

&lt;p&gt;Here are the benchmarks for &lt;code&gt;addressEquals&lt;/code&gt;. I used &lt;a href="https://github.com/dubzzz/fast-check" rel="noopener noreferrer"&gt;&lt;code&gt;fast-check&lt;/code&gt;&lt;/a&gt; to generate the inputs, and &lt;a href="https://github.com/evanwashere/mitata" rel="noopener noreferrer"&gt;&lt;code&gt;mitata&lt;/code&gt;&lt;/a&gt; as the benchmark runner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clk: ~3.02 GHz
cpu: Apple M1 Pro
runtime: node 22.13.1 (arm64-darwin)


  Underscore ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 624.51 ns
      Lodash ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 735.88 ns
      NodeJS ┤■■■■■■■■■■■■■■■■■■■ 420.93 ns
 traversable ┤■■■ 77.56 ns
  FastEquals ┤■■■■ 88.68 ns
 FastIsEqual ┤■■■■■■■■■■■■■ 275.99 ns
  ReactHooks ┤■■■■■■■■■■ 215.57 ns
     JsonJoy ┤■■■■ 91.11 ns
      Effect ┤■■■■■■■■■■ 227.12 ns
zx.deepEqual ┤ 6.77 ns

summary
  zx.deepEqual
   11.45x faster than traversable
   13.1x faster than FastEquals
   13.46x faster than JsonJoy
   31.84x faster than ReactHooks
   33.54x faster than Effect
   40.76x faster than FastIsEqual
   62.17x faster than NodeJS
   92.24x faster than Underscore
   108.69x faster than Lodash

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this, you might enjoy the follow-up, where I use the same technique to "draw the rest of the owl":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/ahrjarrett/how-i-built-javascripts-fastest-deep-clone-function-5fe0"&gt;&lt;em&gt;How to build JavaScript's fastest “deep clone” function&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Updates
&lt;/h2&gt;

&lt;p&gt;Since publishing this article, I've added support for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/pull/259" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/pull/291" rel="noopener noreferrer"&gt;ArkType&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/pull/232" rel="noopener noreferrer"&gt;TypeBox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/traversable/schema/pull/347" rel="noopener noreferrer"&gt;Valibot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bolt.new/~/mitata-ajbdjcot" rel="noopener noreferrer"&gt;Bolt sandbox&lt;/a&gt; where you can run the benchmarks&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/traversable/schema/tree/main/benchmarks" rel="noopener noreferrer"&gt;benchmarks&lt;/a&gt; themselves&lt;/li&gt;
&lt;li&gt;Examples of the &lt;a href="https://github.com/traversable/schema/blob/main/packages/zod/test/deep-equal.test.ts" rel="noopener noreferrer"&gt;generated code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;How the &lt;a href="https://github.com/traversable/schema/blob/main/packages/zod/test/deep-equal.bench.ts" rel="noopener noreferrer"&gt;benchmarks were conducted&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/traversable/schema/blob/main/packages/zod/src/deep-equal.ts" rel="noopener noreferrer"&gt;implementation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webperf</category>
    </item>
    <item>
      <title>The Next Generation of Branded Types in TypeScript</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Fri, 20 Jun 2025 20:17:28 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/the-next-generation-of-branded-types-in-typescript-54jg</link>
      <guid>https://dev.to/ahrjarrett/the-next-generation-of-branded-types-in-typescript-54jg</guid>
      <description>&lt;p&gt;This post will be shorter than most, mostly because writing this has been on my todo list since I &lt;a href="https://github.com/ahrjarrett/any-ts/pull/167" rel="noopener noreferrer"&gt;released the &lt;code&gt;newtype&lt;/code&gt; type 11 months ago&lt;/a&gt;, and since it came up in the &lt;a href="https://discord.com/channels/508357248330760243/508357248330760249/1385519816965754900" rel="noopener noreferrer"&gt;TypeScript discord&lt;/a&gt; yesterday.&lt;/p&gt;

&lt;p&gt;If you're unfamiliar with branded types in TypeScript, this article is not meant to be an introduction to them -- I recommend you read Josh Goldberg's chapter on &lt;a href="https://www.learningtypescript.com/articles/branded-types" rel="noopener noreferrer"&gt;Branded Types&lt;/a&gt;, and then come back.&lt;/p&gt;

&lt;p&gt;tl;dr, about a year ago I released a type called &lt;code&gt;newtype&lt;/code&gt;, that lets you define types like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;newtype&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;any-ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;integer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;integer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;newtype&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myInt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;integer&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ex_01&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myInt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.3&lt;/span&gt;
&lt;span class="c1"&gt;// 🚫 TypeError ^^:&lt;/span&gt;
&lt;span class="c1"&gt;//    Operator '+' cannot be applied to types&lt;/span&gt;
&lt;span class="c1"&gt;//    'integer' and 'number'&lt;/span&gt;

&lt;span class="c1"&gt;// Downcast `myInt` with the unary plus operator:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ex_02&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;myInt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.3&lt;/span&gt;
&lt;span class="c1"&gt;//    ^? const ex_02: number&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Play with it in the &lt;a href="https://tsplay.dev/WPDoJW" rel="noopener noreferrer"&gt;TypeScript Playground&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's happening here?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;newtype&lt;/code&gt; type is part of a library I maintain called &lt;code&gt;any-ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;any-ts&lt;/code&gt; is a TypeScript library, but different than the ones you usually see.&lt;/p&gt;

&lt;p&gt;Rather than shipping a bunch of fancy TypeScript utility types, it ships a bunch of TypeScript &lt;em&gt;primitives&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One of those primitives is &lt;code&gt;newtype&lt;/code&gt;. If you're familiar with Rust or Haskell, this version of &lt;code&gt;newtype&lt;/code&gt; is like a poor person's &lt;code&gt;newtype&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Despite that, it's much more powerful than it looks at first glance. But since this article isn't about promoting my library -- we'll skip them for now.&lt;/p&gt;

&lt;p&gt;Suffice to say, one of the things &lt;code&gt;newtype&lt;/code&gt; lets you do is wrap up a primitive type (like a &lt;code&gt;string&lt;/code&gt; or &lt;code&gt;number&lt;/code&gt;) in a TypeScript interface.&lt;/p&gt;

&lt;p&gt;By doing that, the name you give the type "sticks".&lt;/p&gt;

&lt;p&gt;In other words, instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Integer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;IntegerSymbol&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;myInt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Integer&lt;/span&gt;
&lt;span class="c1"&gt;//          ^? let myInt: number &amp;amp; { [IntegerSymbol]: never }      &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...which is harder to read and leaks implementation details, you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;integer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;newtype&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;myInt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;integer&lt;/span&gt;
&lt;span class="c1"&gt;//          ^? let myInt: integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notably, you can implement every "flavor" (pun intended) of branded type that Josh outlines in his chapter on &lt;a href="https://www.learningtypescript.com/articles/branded-types" rel="noopener noreferrer"&gt;Branded Types&lt;/a&gt;, &lt;em&gt;in terms of&lt;/em&gt; the newtype pattern.&lt;/p&gt;

&lt;p&gt;If you're curious and would like to learn more, I recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;a href="https://discord.com/channels/508357248330760243/508357248330760249/1385519816965754900" rel="noopener noreferrer"&gt;TypeScript discord thread&lt;/a&gt; from yesterday (June 19, 2025)&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://github.com/ahrjarrett/any-ts/pull/167" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; where I talk about some of the interesting possibilities that a type like &lt;code&gt;newtype&lt;/code&gt; opens up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And of course if you have any questions for me, I'm totally open to chatting. I'm on &lt;a href="https://github.com/ahrjarrett" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/ahrjarrett/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, and you can always reach my by email at ahrjarrett at gmail dot com.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>typelevel</category>
    </item>
    <item>
      <title>Inductive Types in TypeScript</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Fri, 16 May 2025 20:11:00 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/inductive-types-in-typescript-1i1e</link>
      <guid>https://dev.to/ahrjarrett/inductive-types-in-typescript-1i1e</guid>
      <description>&lt;p&gt;This post demonstrates a TypeScript pattern I've been experimenting with for the last year or so.&lt;/p&gt;

&lt;p&gt;I've been calling this pattern "inductive type constraints", but perhaps a better name would be "smart constructors", since they have "correct by construction" semantics (here's a &lt;a href="https://dev.to/copyleftdev/correct-by-construction-building-software-right-the-first-time-2hho"&gt;nice article&lt;/a&gt; about them).&lt;/p&gt;

&lt;p&gt;"Smart constructors" definitely sounds cooler, but "inductive types" are what they're called in &lt;a href="https://en.wikipedia.org/wiki/Inductive_type" rel="noopener noreferrer"&gt;other typed languages&lt;/a&gt;, so I went with that.&lt;/p&gt;

&lt;p&gt;Before we dig into how they work, here are a few examples of how they can be used to solve a few long-standing TypeScript issues:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/microsoft/TypeScript/issues/12936#issuecomment-2816816105" rel="noopener noreferrer"&gt;&lt;code&gt;Exact&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"exact types" is historically the &lt;a href="https://github.com/microsoft/TypeScript/issues?q=is%3Aissue%20state%3Aopen%20sort%3Acomments-desc" rel="noopener noreferrer"&gt;2nd-most requested feature&lt;/a&gt; of all time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Exact&lt;/code&gt; ensures that a type does not have any extra properties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the implementation of &lt;code&gt;Exact&lt;/code&gt; (&lt;a href="https://tsplay.dev/WkJJjm" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Exact&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;exact&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Exact&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;

&lt;span class="nf"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;//                         ^ 🚫 nope&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're not sure how or why this works, keep reading -- I talk about that in the second half of this post.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/microsoft/TypeScript/issues/1897#issuecomment-2887684813" rel="noopener noreferrer"&gt;&lt;code&gt;Json&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One of the oldest TypeScript issues (&lt;a href="https://github.com/microsoft/TypeScript/issues/1897" rel="noopener noreferrer"&gt;#187&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the implementation of &lt;code&gt;Json&lt;/code&gt; (&lt;a href="https://tsplay.dev/WkJJjm" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Scalar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Scalar&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Scalar&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;

&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;//   ok ✅&lt;/span&gt;
&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;//   ok ✅&lt;/span&gt;
&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/abc/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;// nope 🚫&lt;/span&gt;
&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;     &lt;span class="c1"&gt;// nope 🚫&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How does it work?
&lt;/h3&gt;

&lt;p&gt;In the &lt;a href="https://discord.com/channels/508357248330760243/508357248330760249/1242010436748972043" rel="noopener noreferrer"&gt;TypeScript discord&lt;/a&gt; I outlined the &lt;a href="https://discord.com/channels/508357248330760243/1242017149518872637/1242256664195760138" rel="noopener noreferrer"&gt;rules&lt;/a&gt; that apply when defining an inductive constraint.&lt;/p&gt;

&lt;p&gt;There are 2 rules you need to follow.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule #1
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're applying a constraint to a type parameter directly, you can't &lt;em&gt;distribute&lt;/em&gt; the parameter or you'll get a circular reference.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What does that look like in practice?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StringLiteral&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; 
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; 
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;StringLiteral&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;


&lt;span class="c1"&gt;///////////////////////////&lt;/span&gt;
&lt;span class="c1"&gt;///    expect: OK ✅    ///&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ex_01&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//  ^? let ex_01: "abc"&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ex_02&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`abc&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;def`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//  ^? let ex_02: `abc${string}def`&lt;/span&gt;


&lt;span class="c1"&gt;//////////////////////////////////////&lt;/span&gt;
&lt;span class="c1"&gt;///    expect: red squiggles 🚫    ///&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://tsplay.dev/WogyeW" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule #2
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of returning &lt;code&gt;T&lt;/code&gt; (the type you're constraining), you need to return the &lt;a href="https://en.wikipedia.org/wiki/Least-upper-bound_property" rel="noopener noreferrer"&gt;least upper bound&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What does that look like in practice?&lt;/p&gt;

&lt;p&gt;This one's a bit more subtle, so I encourage you to play around with the example in the playground to build an intuition for how it works.&lt;/p&gt;

&lt;p&gt;In short: you can't return &lt;code&gt;T&lt;/code&gt; itself, or &lt;code&gt;T&lt;/code&gt; will be circular.&lt;/p&gt;

&lt;p&gt;Instead, &lt;em&gt;return the least constrained&lt;/em&gt; (most permissive) version of the type you're constructing.&lt;/p&gt;

&lt;p&gt;Let's look at the &lt;code&gt;StringLiteral&lt;/code&gt; example again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StringLiteral&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; 
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;   &lt;span class="c1"&gt;//  &amp;lt;-- here&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of returning &lt;code&gt;T&lt;/code&gt;, &lt;em&gt;I return the constraint that I want to apply&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This will feel weird the first few times you do it. Once it clicks, though, if you're like me, you'll gain a new appreciation for one of the many subtleties of the TypeScript type system.&lt;/p&gt;

&lt;p&gt;More examples are in the works, but that's enough for today.&lt;/p&gt;

&lt;p&gt;Inductive types are much more powerful than they might seem at first glance. And they're important to keep in your toolbelt, especially if you're a library author.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>typelevel</category>
    </item>
    <item>
      <title>Recursion in TypeScript (without the tears)</title>
      <dc:creator>andrew jarrett</dc:creator>
      <pubDate>Fri, 21 Mar 2025 13:50:07 +0000</pubDate>
      <link>https://dev.to/ahrjarrett/typesafe-recursion-in-typescript-1pe0</link>
      <guid>https://dev.to/ahrjarrett/typesafe-recursion-in-typescript-1pe0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Principal devs hate this one weird trick...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Have you felt frustrated by how hard recursion is?&lt;/p&gt;

&lt;p&gt;If you've ever tried writing recursive code in TypeScript, all types do is seem to get in the way, and distract you from the real problem you're trying to solve.&lt;/p&gt;

&lt;p&gt;Trust me, I've been there.&lt;/p&gt;

&lt;p&gt;But what if I told you there was a way you could make TypeScript do the hard part for you?&lt;/p&gt;

&lt;p&gt;What if there was a way to make recursion &lt;em&gt;type-safe&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Today I'm going to show you a trick I wish I'd known about circa 2016, when I started on my TypeScript journey.&lt;/p&gt;

&lt;p&gt;Enough preamble: let's look at some code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tsplay.dev/w2y59W" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackblitz.com/edit/vitest-dev-vitest-hv2lxhtc?file=src%2Fjson.ts" rel="noopener noreferrer"&gt;Sandbox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;JSON.map&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To start, let's imagine the built-in &lt;code&gt;JSON&lt;/code&gt; namespace had a &lt;code&gt;.map&lt;/code&gt; method, similar to &lt;code&gt;Array&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How would it behave?&lt;/p&gt;

&lt;p&gt;For starters, it would have to be &lt;em&gt;structure-preserving&lt;/em&gt;, like &lt;code&gt;Array.prototype.map&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What do I mean by structure-preserving?&lt;/p&gt;

&lt;p&gt;Simple: if you map over an array, the array that comes out will always have the same number of elements as the one you put in.&lt;/p&gt;

&lt;p&gt;Same things goes for our imaginary &lt;code&gt;JSON.map&lt;/code&gt; -- only since we also have objects, it will need to preserve the object keys, and only apply the function to object values.&lt;/p&gt;

&lt;p&gt;To make implementing our map function easier, first let's write a function that abstracts away whether we're mapping over an array or an object, since for our purposes, we want to "forget" that there's a difference.&lt;/p&gt;

&lt;p&gt;We'll call it &lt;code&gt;mapObject&lt;/code&gt;, since arrays are also objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mapObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; 
  &lt;span class="nx"&gt;mapFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapFn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we &lt;a href="https://tsplay.dev/NB9JVw" rel="noopener noreferrer"&gt;try it out&lt;/a&gt;, we can see that it works with both objects an arrays, and that in both cases, our implementation is &lt;em&gt;structure-preserving&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now let's implement &lt;code&gt;JSON.map&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Scalar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Scalar&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isScalar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; 
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isArray&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; 
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isArray&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
  &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mapJSON&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mapObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mapObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a lot of code -- let's walk through it:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;Json&amp;lt;T&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First I created a &lt;code&gt;Json&lt;/code&gt; type. Only, this &lt;code&gt;Json&lt;/code&gt; type is a little bit different than what you'll usually see; can you spot this difference?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Scalar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Scalar&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, for starters, this definition of &lt;code&gt;Json&lt;/code&gt; is &lt;strong&gt;not recursive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To see what I mean, let's compare it to the &lt;code&gt;Json&lt;/code&gt; type you'll usually see in the wild:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Scalar&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This definition has its advantages, and its disadvantages.&lt;/p&gt;

&lt;p&gt;The biggest advantage is that it definitely rules out values such as &lt;code&gt;bigint&lt;/code&gt;, &lt;code&gt;symbol&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;s and &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the disadvantage is that the type isn't flexible. It's &lt;em&gt;closed&lt;/em&gt;, rather than &lt;em&gt;open&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What we're trying to do with our &lt;code&gt;JSON.map&lt;/code&gt; function is think about JSON in terms of its &lt;em&gt;structure&lt;/em&gt; -- remember, we're zooming out, and focusing on implementing a &lt;code&gt;JSON.map&lt;/code&gt; that works like &lt;code&gt;Array.map&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here, from where we're standing, we're making the decision to &lt;em&gt;not&lt;/em&gt; focus on the details that make generic data structures like &lt;code&gt;Array&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Map&amp;lt;K, V&amp;gt;&lt;/code&gt; &lt;em&gt;different&lt;/em&gt; than JSON, and instead focus on the ways in which they can be thought of as being &lt;em&gt;the same&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Taking this perspective comes with its advantages, and for our use case, we're mostly interested in 2:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;because this definition does &lt;em&gt;not&lt;/em&gt; rush to constrain its input, &lt;code&gt;Json.map&lt;/code&gt; will be able to take JSON input, and map it to other, non-JSON targets (like &lt;code&gt;bigint&lt;/code&gt;s and &lt;code&gt;Date&lt;/code&gt;s)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;because this definition is &lt;em&gt;not recursive&lt;/em&gt;, it perfectly describes our primary goal, which is to &lt;strong&gt;factor out recursion&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To see what I mean, let's practice some wishful thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wishful thinking
&lt;/h2&gt;

&lt;p&gt;When it comes to recursion, what do we &lt;em&gt;want&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;If you said "to never have to write recursive code", you're well on your way to thinking like a functional programmer!&lt;/p&gt;

&lt;p&gt;Let's explore that idea.&lt;/p&gt;

&lt;p&gt;Let's pretend for a moment that &lt;code&gt;JSON.stringify&lt;/code&gt; isn't a thing. It doesn't exist. And it's our job to implement it.&lt;/p&gt;

&lt;p&gt;How would we go about implementing it? Is there a way to do it that doesn't involve recursion?&lt;/p&gt;

&lt;p&gt;Let's write some pseudo-code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if we've got a scalar value, turn it into a string:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// how are we supposed to get to the values?&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="c1"&gt;// how are we supposed to get to the values?&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How are we supposed to implement this, if we can't call our &lt;code&gt;toString&lt;/code&gt; function on the elements values?&lt;/p&gt;

&lt;p&gt;Well, what if we didn't have to?&lt;/p&gt;

&lt;h3&gt;
  
  
  Reframing the problem
&lt;/h3&gt;

&lt;p&gt;What if, again, practicing some wishful thinking, we instead use &lt;code&gt;Json&amp;lt;string&amp;gt;&lt;/code&gt; instead of &lt;code&gt;Json&amp;lt;T&amp;gt;&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// if we've got a scalar value, turn it into a string:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// how are we supposed to get to the values?&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
    &lt;span class="c1"&gt;// how are we supposed to get to the values?&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See if you can handle the array and object cases, now that we've factored out the recursion.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This part is important.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I highly encourage you to &lt;a href="https://tsplay.dev/mbbE3m" rel="noopener noreferrer"&gt;try it out&lt;/a&gt; in the Playground.&lt;/p&gt;

&lt;p&gt;I can talk all day about how this technique is useful, or about how the technique &lt;em&gt;works&lt;/em&gt;, but if you don't get your hands dirty, things won't stick, and this technique probably won't make any sense.&lt;/p&gt;

&lt;p&gt;I'll give you a few minutes to try it out.&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;Okay, how'd you do?&lt;/p&gt;

&lt;p&gt;Here's what I came up with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tsplay.dev/mA9JPm" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;": &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Was this close to what you came up with?&lt;/p&gt;

&lt;p&gt;If not, don't feel bad: the first time I tried this, I tried too hard. In fact, I was so used to recursion being &lt;em&gt;hard&lt;/em&gt;, that I had a hard time accepting that such a direct approach was possible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But wait a minute... you haven't shown us anything. We're still doing wishful thinking, remember? So far we haven't &lt;em&gt;actually&lt;/em&gt; implemented anything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ah, you're right. I'm getting ahead of myself. We still haven't &lt;em&gt;actually&lt;/em&gt; factored out the recursion. Let's do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Factoring out recursion
&lt;/h2&gt;

&lt;p&gt;For this next part, you're going to have to trust me that &lt;em&gt;you'll never have to actually write this code yourself&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;By that I mean, it's &lt;a href="https://github.com/traversable/schema/blob/main/packages/registry/src/function.ts#L67-L78" rel="noopener noreferrer"&gt;possible&lt;/a&gt; to implement a generalized version of this function.&lt;/p&gt;

&lt;p&gt;Again, this isn't the kind of thing you'll need to "roll yourself" every time you have a new data structure you need to work with.&lt;/p&gt;

&lt;p&gt;Okay, with that out of the way, here's the code that takes care of the recursion for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;lift&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;foldJson&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nf"&gt;lift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;I've got another article planned where I talk through exactly &lt;em&gt;why&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; this function works, but it's worth noting that I didn't come up with it myself -- I simply &lt;a href="https://github.com/recursion-schemes/recursion-schemes" rel="noopener noreferrer"&gt;ported it&lt;/a&gt; from the Haskell library that first introduced this technique.&lt;/p&gt;

&lt;p&gt;Okay, that was the sweaty part.&lt;/p&gt;

&lt;p&gt;Does it actually work?&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing it in use
&lt;/h2&gt;

&lt;p&gt;See for yourself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/edit/shallow-stringify?file=src%2Fjson.ts" rel="noopener noreferrer"&gt;Sandbox&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! Let's recap:&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;To make working with recursive code easier, we explored a way we can "factor out" recursion.&lt;/p&gt;

&lt;p&gt;To simplify things, we chose to focus on only JSON values, and to make things concrete, we chose just to focus on implementing a naive version of &lt;code&gt;JSON.stringify&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To get there, we had to make a few decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To make things type-safe, we opted for a non-traditional, non-recursive definition of &lt;code&gt;Json&lt;/code&gt;, which looked like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; 
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; 
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; 
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Then, we used this type to implement a utility function called &lt;code&gt;mapJson&lt;/code&gt; that let us zoom out a bit, and apply a function to a JSON value without knowing anything about its structure in advance. Importantly, this &lt;code&gt;map&lt;/code&gt; function &lt;em&gt;preserves structure&lt;/em&gt; -- meaning that the value that we get out will have the same "shape" as the value we put in -- the only thing that will change are the leaf values.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That map function looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mapJson&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mapObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mapObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We leaned on some "library code" to handle the recursive bit. I won't put that code here, because it's not important that we fully grok how that code &lt;em&gt;works&lt;/em&gt; for us to use it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, we implemented a naive version of &lt;code&gt;Json.stringify&lt;/code&gt;. Notably, this implementation was &lt;em&gt;not recursive&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;": &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The takeaway here is that because this implementation is &lt;em&gt;not recursive&lt;/em&gt;, it's also 100% type-safe. Here, TypeScript was our friend, since when we went to implement the array and object cases, it was there to remind us that we were working with &lt;code&gt;string[]&lt;/code&gt; and &lt;code&gt;{ [x: string]: string }&lt;/code&gt;, and not &lt;code&gt;unknown[]&lt;/code&gt; or &lt;code&gt;{ [x: string]: unknown }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, we put all the pieces together, wrote a few tests, and made sure it all worked!&lt;/p&gt;

&lt;p&gt;The full implementation + tests is available &lt;a href="https://stackblitz.com/edit/vitest-dev-vitest-hv2lxhtc?file=src%2Fjson.ts" rel="noopener noreferrer"&gt;in a Sandbox&lt;/a&gt; that you can play with.&lt;/p&gt;

&lt;p&gt;If you're only interested in the types, you can check out the &lt;a href="https://tsplay.dev/w2y59W" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Phew! That's a lot for one day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;It's important when you're learning something new to balance the theory with practice -- and to not overload yourself with too much information -- so I think we should call it here.&lt;/p&gt;

&lt;p&gt;Congratulations! If this is the first time you've ever used recursion schemes before, you're not alone -- this technique, although quite popular in the functional programming world, is &lt;a href="https://github.com/search?q=recursion+schemes+language%3Atypescript&amp;amp;type=repositories" rel="noopener noreferrer"&gt;virtually unheard&lt;/a&gt; of in the TypeScript ecosystem.&lt;/p&gt;

&lt;p&gt;If you're hungry for more, I went ahead and included an example in the &lt;a href="https://stackblitz.com/edit/vitest-dev-vitest-hv2lxhtc?file=src%2Fjson.ts%3AL82" rel="noopener noreferrer"&gt;Sandbox&lt;/a&gt; that applied the same technique to solve a notoriously tricky recursive problem: turning a deeply nested JSON object into a flat object whose keys are the paths, and whose values are the corresponding value at that path.&lt;/p&gt;

&lt;p&gt;I've called it &lt;code&gt;Json.toPaths&lt;/code&gt;, since that's the name most commonly used to describe this operation in the JavaScript ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugs
&lt;/h3&gt;

&lt;p&gt;If you liked this kind of content, you might be interested in a library that I wrote called &lt;a href="https://github.com/traversable/schema/" rel="noopener noreferrer"&gt;&lt;code&gt;@traversable/schema&lt;/code&gt;&lt;/a&gt;. It's a TypeScript schema library -- kinda like a lightweight zod (faster at runtime, and scales to arbitrarily large schemas without slowing down your IDE). Recursion schemes are at the heart of its implementation -- it even includes the same &lt;a href="https://github.com/traversable/schema/blob/main/packages/json/src/functor.ts#L119-L128" rel="noopener noreferrer"&gt;&lt;code&gt;Json.map&lt;/code&gt;&lt;/a&gt; we implemented earlier.&lt;/p&gt;

&lt;p&gt;And if you're hiring TypeScript developers and liked this content, I'm in the market -- let's talk! Feel free to email me at &lt;a href="mailto:ahrjarrett@gmail.com"&gt;ahrjarrett@gmail.com&lt;/a&gt; if you'd like a copy of my resume.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>recursion</category>
      <category>typesafe</category>
      <category>vitest</category>
    </item>
  </channel>
</rss>
