<?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: Kazuya Umeki</title>
    <description>The latest articles on DEV Community by Kazuya Umeki (@umekikazuya).</description>
    <link>https://dev.to/umekikazuya</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%2F4125344%2F49207311-d054-4e4a-b1de-26ce44a9c3d4.png</url>
      <title>DEV Community: Kazuya Umeki</title>
      <link>https://dev.to/umekikazuya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/umekikazuya"/>
    <language>en</language>
    <item>
      <title>PATCH vs. "Drop Null Properties": Two Google-Style Ways to Clear a Field</title>
      <dc:creator>Kazuya Umeki</dc:creator>
      <pubDate>Tue, 29 Sep 2026 20:45:31 +0000</pubDate>
      <link>https://dev.to/umekikazuya/patch-vs-drop-null-properties-two-google-style-ways-to-clear-a-field-336c</link>
      <guid>https://dev.to/umekikazuya/patch-vs-drop-null-properties-two-google-style-ways-to-clear-a-field-336c</guid>
      <description>&lt;p&gt;If your API drops null properties from its JSON, &lt;code&gt;PATCH&lt;/code&gt; can no longer tell "clear this field" apart from "leave it alone." In this post, I'll show two ways I've solved that while still following Google's API guidelines.&lt;/p&gt;

&lt;p&gt;When designing APIs, I often refer to Google's AIPs (API Improvement Proposals) and Style Guides.&lt;/p&gt;

&lt;p&gt;The Google JSON Style Guide has a rule that says to "consider removing properties with null values."&lt;/p&gt;

&lt;p&gt;In other words, for a &lt;code&gt;Users&lt;/code&gt; resource with &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt;, if &lt;code&gt;age&lt;/code&gt; is unset (&lt;code&gt;NULL&lt;/code&gt;), the recommended JSON looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazuya Umeki"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adopting this rule, however, creates a problem when implementing PATCH (partial updates).&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, the "drop nulls" rule and PATCH can coexist&lt;/strong&gt; (or so I believe).&lt;/p&gt;

&lt;p&gt;The rule is a recommendation ("consider removing"), and it explicitly allows an exception when there's a strong semantic reason to keep the property.&lt;br&gt;
You could argue that wanting to clear a value is exactly that kind of reason. But then every client has to send &lt;code&gt;null&lt;/code&gt; on purpose, and the rule stops being a rule.&lt;/p&gt;

&lt;p&gt;If clients also omit nulls in their requests (the rule applies to requests too), the server can no longer distinguish "not specified" from "clear this value."&lt;/p&gt;

&lt;p&gt;So the intent has to travel through a different channel. These are the two channels I've used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Approach 1&lt;/strong&gt;: Turn "unset" into a value (enum zero value, AIP-126). Good for enum fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approach 2&lt;/strong&gt;: Explicitly list the fields to update (&lt;code&gt;update_mask&lt;/code&gt;, AIP-134). Works for any type.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The "Empty/Null Property Values" rule
&lt;/h3&gt;

&lt;p&gt;The Google JSON Style Guide has a rule called &lt;a href="https://google.github.io/styleguide/jsoncguide.html#Empty/Null_Property_Values" rel="noopener noreferrer"&gt;Empty/Null Property Values&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Consider removing empty or null values.&lt;/p&gt;

&lt;p&gt;If a property is optional or has an empty or null value, consider dropping the property from the JSON, unless there's a strong semantic reason for its existence.&lt;/p&gt;


&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Even&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;though&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"balance"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;property's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;since&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;signifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"even balance"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;could&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;left&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;balance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;balance.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"currentlyPlaying"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;since&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"currentlyPlaying"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Note: The comments in the quote are for illustration only; JSON doesn't actually allow comments.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In short: "Unless there's a particular reason, remove empty or NULL properties from the JSON."&lt;br&gt;
In the example above, &lt;code&gt;balance&lt;/code&gt; can be &lt;code&gt;-1&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt;, so &lt;code&gt;0&lt;/code&gt; carries meaning. My reading is that this is why it's kept rather than omitted.&lt;/p&gt;

&lt;p&gt;The rule applies to both requests and responses.&lt;br&gt;
The idea is to keep a property only when there's a strong reason (i.e., the value carries meaning).&lt;/p&gt;

&lt;p&gt;Any policy would work, but with strictly typed languages becoming more common, it's nice to have a rule like this in place.&lt;br&gt;
You don't have to agonize over &lt;code&gt;undefined&lt;/code&gt; vs. &lt;code&gt;null&lt;/code&gt;... until PATCH shows up.&lt;/p&gt;
&lt;h3&gt;
  
  
  PATCH in a nutshell
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The PATCH HTTP method applies partial modifications to a resource.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/PATCH" rel="noopener noreferrer"&gt;PATCH request method - MDN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;PUT&lt;/code&gt; replaces the whole resource; &lt;code&gt;PATCH&lt;/code&gt; changes only part of it.&lt;br&gt;
Whether a &lt;code&gt;PATCH&lt;/code&gt; is idempotent depends on its design. Both approaches in this post only &lt;em&gt;set&lt;/em&gt; values, so their requests stay idempotent and safe to retry.&lt;/p&gt;

&lt;p&gt;Here's a side-by-side comparison:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'PATCH'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.example.com/users/{id}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "Kazuya Umeki"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'PUT'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.example.com/users/{id}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "Kazuya Umeki", "age": 25}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wait, we can't clear age?
&lt;/h3&gt;

&lt;p&gt;Some of you may have noticed already: with &lt;code&gt;PATCH&lt;/code&gt;, clearing &lt;code&gt;age&lt;/code&gt; turns out to be a problem.&lt;/p&gt;

&lt;p&gt;Say the resource is currently in this state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazuya U."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a thought crosses your mind: "Hmm, I'd rather take my age off my profile."&lt;/p&gt;

&lt;p&gt;Under the rule, sending &lt;code&gt;"age": null&lt;/code&gt; isn't an option, so omitting it is all the client can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'PATCH'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.example.com/users/{id}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "Kazuya Umeki"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the server reads this as "A &lt;code&gt;PATCH&lt;/code&gt; request came in. So this is a request to update &lt;code&gt;name&lt;/code&gt;." The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kazuya Umeki"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once a value has been entered, there's no way to clear it.&lt;/p&gt;




&lt;p&gt;To sum up, a PATCH body inherently has three states:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;JSON following the rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Has a value&lt;/td&gt;
&lt;td&gt;Update to this value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"age": 30&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;Clear the value&lt;/td&gt;
&lt;td&gt;Property omitted (indistinguishable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not specified&lt;/td&gt;
&lt;td&gt;Don't touch it&lt;/td&gt;
&lt;td&gt;Property omitted (indistinguishable)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The rule of omitting NULLs erases the distinction between "clear it" and "leave it alone."&lt;/strong&gt;&lt;br&gt;
That's the root of the problem. In TypeScript terms, it's the difference between &lt;code&gt;age?: number&lt;/code&gt; and &lt;code&gt;age?: number | null&lt;/code&gt;. Strictly typed languages like TypeScript and Go make you want to keep those three states apart.&lt;/p&gt;



&lt;p&gt;In the rest of this post, I'll show how I let clients clear a field they've already set.&lt;br&gt;
&lt;code&gt;age&lt;/code&gt; can't be enumerated, so for Approach 1 I'll switch to a &lt;code&gt;gender&lt;/code&gt; field. Approach 2 goes back to &lt;code&gt;age&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Options I considered first
&lt;/h2&gt;

&lt;p&gt;Before getting to my two approaches, here are the other standard options and where they clash with the rule.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicitly sending NULL&lt;/strong&gt; (&lt;code&gt;{"age": null}&lt;/code&gt;): It breaks the rule for requests, so every client serializer has to keep nulls on purpose. On the server, Go's &lt;code&gt;encoding/json&lt;/code&gt; decodes both &lt;code&gt;null&lt;/code&gt; and a missing key into a &lt;code&gt;nil&lt;/code&gt; pointer, so telling them apart needs a custom type for every clearable field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Merge Patch, RFC 7396&lt;/strong&gt; (&lt;code&gt;{"age": null}&lt;/code&gt; removes the property): Same problem in a standard wrapper: clients must send &lt;code&gt;null&lt;/code&gt;, which is exactly what the rule tells them not to do. It also needs its own media type (&lt;code&gt;application/merge-patch+json&lt;/code&gt;), and it replaces arrays wholesale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Patch, RFC 6902&lt;/strong&gt; (a &lt;code&gt;remove&lt;/code&gt; operation in an array of operations): Very expressive, but clients have to build an operation list with JSON Pointer paths instead of sending the resource. That's a lot of ceremony for a form that edits a few fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applying the rule to responses only&lt;/strong&gt; (&lt;code&gt;{"age": null}&lt;/code&gt; allowed in requests): Two rules for one API. Clients and reviewers have to remember which direction allows &lt;code&gt;null&lt;/code&gt;, and the server still has the Go decoding problem from the first item.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be fair, sending &lt;code&gt;null&lt;/code&gt; isn't unheard of even at Google: its Go client libraries have a &lt;code&gt;NullFields&lt;/code&gt; option for sending nulls in PATCH requests. It's a real trade-off. I just wanted one rule in both directions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Approach 1: Operation AIP-126
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;(The "Operation" names were coined with a coworker. Google had nothing to do with them.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://google.aip.dev/" rel="noopener noreferrer"&gt;AIP&lt;/a&gt; (API Improvement Proposals) is a set of design documents Google maintains as guidance for API design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://google.aip.dev/126" rel="noopener noreferrer"&gt;AIP-126&lt;/a&gt; covers enums. It recommends making the first value of an enum &lt;code&gt;*_UNSPECIFIED&lt;/code&gt; (with an exception for a useful zero value like &lt;code&gt;UNKNOWN&lt;/code&gt;), and its own example documents that value as unused.&lt;br&gt;
I deliberately gave it a meaning, "not set," so clients can send it to clear the field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;gender&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;genderUnspecified&lt;/span&gt; &lt;span class="n"&gt;gender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"unspecified"&lt;/span&gt;
    &lt;span class="n"&gt;genderMale&lt;/span&gt;        &lt;span class="n"&gt;gender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"male"&lt;/span&gt;
    &lt;span class="n"&gt;genderFemale&lt;/span&gt;      &lt;span class="n"&gt;gender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"female"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Simplified for illustration. A real form usually needs more options; see the limitations below.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Clearing the field is then just a normal request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'PATCH'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.example.com/users/{id}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"gender": "unspecified"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, I was weighing how to handle optionals: "Should I use a pointer type?" "Should I keep it a &lt;code&gt;string&lt;/code&gt; and just omit the property?"&lt;br&gt;
But I realized that if I &lt;strong&gt;treat the unset state as a value&lt;/strong&gt;, handling becomes consistent across the API, DB, and UI layers. (In the DB, you can keep storing &lt;code&gt;NULL&lt;/code&gt; and map it to &lt;code&gt;"unspecified"&lt;/code&gt; at the API boundary, so existing rows don't need a migration.)&lt;/p&gt;

&lt;p&gt;One Go-specific catch: the zero value of &lt;code&gt;gender&lt;/code&gt; is &lt;code&gt;""&lt;/code&gt;, not &lt;code&gt;"unspecified"&lt;/code&gt;. That turns out to be what makes this approach work. When a client omits &lt;code&gt;gender&lt;/code&gt;, the decoder gives you &lt;code&gt;""&lt;/code&gt;, which should mean "leave it alone." &lt;code&gt;"unspecified"&lt;/code&gt; is the only way to say "clear it." So don't normalize &lt;code&gt;""&lt;/code&gt; to &lt;code&gt;"unspecified"&lt;/code&gt;: every PATCH that omits &lt;code&gt;gender&lt;/code&gt; would wipe it. (&lt;code&gt;encoding/json&lt;/code&gt; can't tell an omitted field from an explicit &lt;code&gt;"gender": ""&lt;/code&gt;, so treat both as "not specified." If you unmarshal straight onto the loaded entity instead, omitted fields simply keep their current value.)&lt;/p&gt;

&lt;p&gt;This approach has limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only works for fields you can enumerate. You can't do this for &lt;code&gt;age&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Whether "not set" and "prefer not to say" can share one value depends on your requirements. If your form offers "Prefer not to say," it probably needs its own value.&lt;/li&gt;
&lt;li&gt;Adding values later means old clients must tolerate unknown strings. (The JSON Style Guide recommends string enum values for exactly this reason.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which brings us to Approach 2.&lt;/p&gt;
&lt;h2&gt;
  
  
  Approach 2: Operation UpdateMask
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://google.aip.dev/134" rel="noopener noreferrer"&gt;AIP-134&lt;/a&gt; (Standard methods: Update) already defines this for HTTP: the resource goes in the PATCH body, and &lt;code&gt;update_mask&lt;/code&gt; goes in the query string.&lt;br&gt;
The JSON Style Guide itself hints at the same idea: it reserves a &lt;code&gt;data.fields&lt;/code&gt; property that lists the fields present in a partial PATCH request.&lt;br&gt;
Our API isn't gRPC, but I adopted the same contract for our plain REST/JSON endpoints. I built a PoC, adopted it, and implemented it.&lt;/p&gt;

&lt;p&gt;In short, it's a technique for &lt;strong&gt;explicitly listing which properties to update&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's easier to show than explain, so take a look at the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'PATCH'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.example.com/users/{id}?update_mask=name,age"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "Kazuya Umeki"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;List the properties you want to update as a comma-separated query parameter, e.g. &lt;code&gt;update_mask=name,age&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The request body expresses the resulting state (with NULL values omitted).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the server's perspective, it goes like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An update request for a user resource came in!&lt;/li&gt;
&lt;li&gt;The query parameter says there are two targets: &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt;!&lt;/li&gt;
&lt;li&gt;Looking at the body, &lt;code&gt;name&lt;/code&gt; gets updated to &lt;code&gt;Kazuya Umeki&lt;/code&gt;, and &lt;code&gt;age&lt;/code&gt; isn't in the body, so it gets &lt;strong&gt;cleared (set to NULL)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this approach, you can &lt;strong&gt;distinguish "clear it" from "leave it alone" while still following the rule&lt;/strong&gt;, which solves the original problem.&lt;/p&gt;

&lt;p&gt;Here's how each combination is interpreted:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Server's interpretation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;update_mask=name&lt;/code&gt; + body &lt;code&gt;{"name": "A"}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Update only &lt;code&gt;name&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;update_mask=name,age&lt;/code&gt; + body &lt;code&gt;{"name": "A"}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Update &lt;code&gt;name&lt;/code&gt; and clear &lt;code&gt;age&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;update_mask=name&lt;/code&gt; + body &lt;code&gt;{"name": "A", "age": 30}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Update only &lt;code&gt;name&lt;/code&gt;; &lt;code&gt;age&lt;/code&gt; is ignored (&lt;a href="https://google.aip.dev/161" rel="noopener noreferrer"&gt;AIP-161&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;update_mask=age&lt;/code&gt; + body &lt;code&gt;{"age": null}&lt;/code&gt; (a client ignoring the rule)&lt;/td&gt;
&lt;td&gt;Treat it the same as omitting &lt;code&gt;age&lt;/code&gt;: clear it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No mask&lt;/td&gt;
&lt;td&gt;Update every field present in the body (AIP-134), so nothing can be cleared&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;update_mask=*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full replacement, like &lt;code&gt;PUT&lt;/code&gt; (AIP-134 requires supporting it, but recommends listing fields explicitly)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The response is the updated resource, as AIP-134 recommends, so the client can see right away that &lt;code&gt;age&lt;/code&gt; is gone.&lt;/p&gt;

&lt;p&gt;About casing: AIP names the field &lt;code&gt;update_mask&lt;/code&gt;, but in JSON a FieldMask is encoded as a comma-separated string of lowerCamelCase paths (e.g., &lt;code&gt;profile.displayName&lt;/code&gt;). A practical choice is to &lt;strong&gt;accept both &lt;code&gt;update_mask&lt;/code&gt; and &lt;code&gt;updateMask&lt;/code&gt; as the parameter name, and use the same lowerCamelCase paths as the JSON body&lt;/strong&gt;, so clients can put body keys straight into the mask.&lt;/p&gt;

&lt;p&gt;Here's a minimal sketch of applying the mask in Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"name,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;Age&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;   &lt;span class="s"&gt;`json:"age,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// applyMask copies only the masked fields from patch onto current.&lt;/span&gt;
&lt;span class="c"&gt;// A field that is in the mask but missing from the body gets cleared.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;applyMask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"age"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="c"&gt;// nil when omitted → cleared (NULL)&lt;/span&gt;
        &lt;span class="c"&gt;// "*" and nested paths are omitted for brevity.&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid update_mask path: %q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&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="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A handler would split the query parameter on commas, call &lt;code&gt;applyMask&lt;/code&gt; on the loaded user, and then validate the &lt;strong&gt;merged&lt;/strong&gt; user before saving. That last step is what stops &lt;code&gt;update_mask=name&lt;/code&gt; with an empty body from blanking a required field.&lt;/p&gt;

&lt;p&gt;There are also decisions to make when implementing this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;A reasonable default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fields that must never be updated (&lt;code&gt;id&lt;/code&gt;, permissions) appear in the mask&lt;/td&gt;
&lt;td&gt;Ignore output-only fields like &lt;code&gt;id&lt;/code&gt;, as AIP-161 requires. Check fields the caller isn't allowed to change (e.g., roles) against an allowlist and reject them with 400.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A path in the mask doesn't exist&lt;/td&gt;
&lt;td&gt;Return 400, matching AIP-161's recommendation of &lt;code&gt;INVALID_ARGUMENT&lt;/code&gt; for writes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nested fields&lt;/td&gt;
&lt;td&gt;Use dot-separated paths such as &lt;code&gt;profile.displayName&lt;/code&gt;. AIP-161 requires allowing either the whole object or a single subfield.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supporting &lt;code&gt;*&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Support it, since AIP-134 requires it, but have your own clients list fields explicitly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the mask required? (affects compatibility with existing clients; AIP-134 makes it optional)&lt;/td&gt;
&lt;td&gt;Keep it optional. Without a mask, only fields present in the body are updated, so existing clients keep working. They just can't clear anything.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Whatever you choose, &lt;strong&gt;validate the merged resource, not just the request&lt;/strong&gt;. Otherwise, a mask can silently clear a required field.&lt;/p&gt;

&lt;p&gt;On the client side, a simple way is to build the mask from &lt;strong&gt;the form's dirty fields: every field the user changed goes into the mask, and a field the user emptied is left out of the body&lt;/strong&gt;. That's all it takes to express "clear it."&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the two approaches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Approach 1: Operation AIP-126&lt;/th&gt;
&lt;th&gt;Approach 2: Operation UpdateMask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pros&lt;/td&gt;
&lt;td&gt;Requests stay plain JSON. Simple to implement.&lt;/td&gt;
&lt;td&gt;Works with any type. Follows AIP-134's contract.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cons&lt;/td&gt;
&lt;td&gt;Only works for enums. Gives "unspecified" a meaning AIP-126 doesn't assign it, and takes the field out of the rule's scope.&lt;/td&gt;
&lt;td&gt;Mask and body must be kept in sync. More work for clients. Many behaviors to define.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which one should you pick?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The field is an enum, and you want requests to stay plain JSON&lt;/strong&gt; → Approach 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The field isn't an enum, or many fields need to be clearable&lt;/strong&gt; → Approach 2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You don't control the clients (e.g., a public API)&lt;/strong&gt; → weigh Approach 2's extra client work more heavily.&lt;/li&gt;
&lt;li&gt;Nothing stops you from combining them: enum fields get an explicit unspecified value, and everything else goes through the mask. If you do, let the mask win: a masked field that's missing from the body is cleared, even if it's an enum.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;In this post, I focused on HTTP PATCH requests and introduced two approaches.&lt;/p&gt;

&lt;p&gt;Both approaches keep the drop-null rule intact. They just move the intent somewhere the rule doesn't reach: into the value (Approach 1) or into the mask (Approach 2). I believe a good API is one where "the operation a request wants to perform on the resource is clear." I hope you found something to take back to your own API development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aside: Why I lean on Google's style guides
&lt;/h2&gt;

&lt;p&gt;Most major OSS projects have their own style guide, and Google publishes the ones it uses, such as the &lt;a href="https://google.github.io/styleguide/jsoncguide.html" rel="noopener noreferrer"&gt;JSON Style Guide&lt;/a&gt;, &lt;a href="https://google.github.io/styleguide/go/" rel="noopener noreferrer"&gt;Go Style&lt;/a&gt;, and the &lt;a href="https://google.github.io/styleguide/docguide/style.html" rel="noopener noreferrer"&gt;Markdown Style Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did review style guides from a few other companies, but Google's usually explain the reasons and trade-offs behind each rule. They're also well known, which lowers the cost of adopting them and makes code reviews and new projects easier. You can adopt them as-is or fork and customize them.&lt;/p&gt;

&lt;p&gt;They're still only guidelines, though. For Go, for example, I put &lt;code&gt;Effective Go&lt;/code&gt; and &lt;code&gt;gofmt&lt;/code&gt; first and treat Google's guide as a supplement.&lt;/p&gt;

&lt;p&gt;The style guides have a lot to offer, so I recommend browsing them when you need a break.&lt;/p&gt;

&lt;p&gt;Thanks for reading all the way to the end!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Descriptions of the AIPs and the Google JSON Style Guide reflect what I checked as of September 2026. AIP content is licensed under CC BY 4.0.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>go</category>
      <category>rest</category>
    </item>
  </channel>
</rss>
