<?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: ITFabers</title>
    <description>The latest articles on DEV Community by ITFabers (@itfabers).</description>
    <link>https://dev.to/itfabers</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4060282%2F33e51341-4f1a-40ff-a06e-54b9a9fa882b.png</url>
      <title>DEV Community: ITFabers</title>
      <link>https://dev.to/itfabers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/itfabers"/>
    <language>en</language>
    <item>
      <title>Generating request payloads for `oneOf` with `discriminator` in OpenAPI, without generating garbage</title>
      <dc:creator>ITFabers</dc:creator>
      <pubDate>Sun, 06 Sep 2026 09:32:12 +0000</pubDate>
      <link>https://dev.to/itfabers/generating-request-payloads-for-oneof-with-discriminator-in-openapi-without-generating-garbage-koj</link>
      <guid>https://dev.to/itfabers/generating-request-payloads-for-oneof-with-discriminator-in-openapi-without-generating-garbage-koj</guid>
      <description>&lt;p&gt;I spent most of a weekend on a bug that boiled down to one wrong assumption: that &lt;code&gt;oneOf&lt;/code&gt; and &lt;code&gt;discriminator&lt;/code&gt; are basically the same feature. They're not, and if your payload generator treats them as interchangeable, it'll produce technically-valid JSON that fails validation on the server every single time.&lt;/p&gt;

&lt;p&gt;Here's the setup that broke things. A payments API with a &lt;code&gt;PaymentMethod&lt;/code&gt; schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;PaymentMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;oneOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/CardPayment'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/BankTransfer'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/PayPalPayment'&lt;/span&gt;
  &lt;span class="na"&gt;discriminator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;propertyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type&lt;/span&gt;
    &lt;span class="na"&gt;mapping&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/CardPayment'&lt;/span&gt;
      &lt;span class="na"&gt;bank_transfer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/BankTransfer'&lt;/span&gt;
      &lt;span class="na"&gt;paypal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/PayPalPayment'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Naive generators do one of two things wrong. First mistake: pick a random branch from &lt;code&gt;oneOf&lt;/code&gt;, build the fields, and forget to set &lt;code&gt;type&lt;/code&gt; at all — because nothing in the branch schema itself says the field is required if the discriminator lives at the parent level. Second mistake: set &lt;code&gt;type&lt;/code&gt; to the schema's own name (&lt;code&gt;CardPayment&lt;/code&gt;) instead of the mapped value (&lt;code&gt;card&lt;/code&gt;), because the mapping block is easy to skip if you're just walking &lt;code&gt;oneOf&lt;/code&gt; and resolving refs.&lt;/p&gt;

&lt;p&gt;Both produce a payload that parses as valid JSON, passes basic schema checks in a lot of naive validators, and then gets rejected by the actual API because the discriminator value doesn't match anything the server recognizes.&lt;/p&gt;

&lt;p&gt;The fix is to treat discriminator resolution as its own pass, separate from picking a &lt;code&gt;oneOf&lt;/code&gt; branch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resolve the &lt;code&gt;oneOf&lt;/code&gt; branch first — for coverage you want to generate a payload per branch, not just one.&lt;/li&gt;
&lt;li&gt;For each branch, look up whether it's referenced in &lt;code&gt;discriminator.mapping&lt;/code&gt;. If there's no explicit mapping, fall back to the implicit rule: the discriminator value is the schema's &lt;code&gt;$ref&lt;/code&gt; name.&lt;/li&gt;
&lt;li&gt;Force-set &lt;code&gt;propertyName&lt;/code&gt; on the generated object to that resolved value, overwriting whatever the branch's own field generation would have produced (a naive string generator will happily fill &lt;code&gt;type&lt;/code&gt; with &lt;code&gt;"lorem ipsum"&lt;/code&gt; if you let it).&lt;/li&gt;
&lt;li&gt;Only then generate the rest of the branch's required fields.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The other trap is &lt;code&gt;allOf&lt;/code&gt; + discriminator, which is the more common real-world shape — a base object with shared fields, extended per-variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;CardPayment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/PaymentMethod'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;card_number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;expiry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the discriminator property doesn't even live in the branch schema — it's on the base. If your generator resolves &lt;code&gt;allOf&lt;/code&gt; by shallow-merging properties, this works fine. If it resolves refs lazily or caches merged schemas by name, you can end up with all three payment variants sharing one mutated base object and one clobbering another's discriminator value. That one took longer to find than I'd like to admit — it only showed up when generating multiple variants in the same test run, not in isolation.&lt;/p&gt;

&lt;p&gt;I ended up writing this exact resolution order into apitestgen.dev after finding it the hard way on a real spec. If you're building anything that walks OpenAPI schemas to synthesize data — test payloads, mocks, fixtures — discriminator resolution deserves its own explicit step. Don't let it fall out of your &lt;code&gt;oneOf&lt;/code&gt; branch-picking logic as a side effect.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>api</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
