October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk6 min

How to Test JSON PATCH Requests for Missing, Null, and Invalid Fields in Go

A Go PATCH test should verify the patch format, decoded field presence, HTTP response, and final resource state. Here is how to cover missing, null, and invalid JSON fields.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To test a Go PATCH endpoint correctly, first establish which patch format and media type it accepts. Then test omitted fields, explicit null, valid replacements, and invalid input separately—and assert both the HTTP response and the resource’s final state. A plain Go pointer field often cannot distinguish an omitted member from one explicitly set to null.

Start with the endpoint’s patch contract

HTTP PATCH does not prescribe one universal meaning for a JSON body. RFC 5789 defines PATCH as applying changes described in a patch document; the document’s media type identifies its format. Check the endpoint documentation and Content-Type handling before deciding what a test should expect. An endpoint can advertise supported formats with Accept-Patch. RFC 5789

The API contract—not a generic testing rule—determines whether a particular null, malformed value, or unknown member is accepted, what status and error body it returns, and whether unknown members are ignored or rejected.

Why a Go pointer does not always distinguish missing from null

With legacy encoding/json struct decoding, an omitted JSON member leaves the destination field unchanged. Explicit JSON null sets pointers, maps, slices, and interfaces to nil; for most other Go types, null has no effect and does not itself produce an error. Consequently, decoding a fresh request into a struct with Name *string can leave Name nil both when name is absent and when it is explicitly null. Go encoding/json documentation

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If absence means “leave unchanged” but null means “clear,” preserve member presence explicitly. One option is a wrapper with a presence flag and custom UnmarshalJSON; another is decoding the object into raw members and checking whether the key exists before decoding its value. Test the exact decoder, Go version, and options your service uses: other JSON packages or newer APIs may behave differently.

A presence-aware field wrapper

This wrapper records whether the field appeared, while its pointer distinguishes null from a decoded string:

type OptionalString struct {
    Present bool
    Value   *string
}

func (o *OptionalString) UnmarshalJSON(data []byte) error {
    o.Present = true
    if bytes.Equal(bytes.TrimSpace(data), []byte("null")) {
        o.Value = nil
        return nil
    }

    var value string
    if err := json.Unmarshal(data, &value); err != nil {
        return err
    }
    o.Value = &value
    return nil
}

type PatchRequest struct {
    Name OptionalString `json:"name"`
}

With a newly initialized request value, Present == false means omitted; Present == true with Value == nil means explicit null; and a non-nil value means a decoded string. The string decode rejects values of incompatible JSON types. If request objects may be reused, reset them before decoding so the presence flag cannot carry over from an earlier decode.

Test the three decoded states directly

A small decoder-level test isolates presence semantics from routing and persistence. These examples assume the wrapper above and the standard library decoder:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
func TestPatchNamePresence(t *testing.T) {
    tests := []struct {
        name        string
        body        string
        wantPresent bool
        wantValue   *string
        wantErr     bool
    }{
        {name: "omitted", body: `{}`, wantPresent: false},
        {name: "null", body: `{"name":null}`, wantPresent: true},
        {name: "value", body: `{"name":"Ada"}`, wantPresent: true, wantValue: ptr("Ada")},
        {name: "wrong type", body: `{"name":42}`, wantPresent: true, wantErr: true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            var got PatchRequest
            err := json.Unmarshal([]byte(tt.body), &got)
            if (err != nil) != tt.wantErr {
                t.Fatalf("Unmarshal error = %v, wantErr %v", err, tt.wantErr)
            }
            if got.Name.Present != tt.wantPresent {
                t.Fatalf("Present = %v, want %v", got.Name.Present, tt.wantPresent)
            }
            if tt.wantErr {
                return
            }
            if !reflect.DeepEqual(got.Name.Value, tt.wantValue) {
                t.Fatalf("Value = %v, want %v", got.Name.Value, tt.wantValue)
            }
        })
    }
}

For example, ptr can be a test helper that returns the address of a string. The test checks the decoded representation before update logic interprets it; separately test whether the endpoint maps null to clearing, rejection, or another documented outcome.

Exercise the actual handler with table-driven requests

Use httptest.NewRequest and httptest.NewRecorder to send requests through the handler’s normal routing, decoding, validation, and update path. Set the media type the endpoint actually supports rather than assuming that every PATCH endpoint accepts application/json. Go documents httptest.NewRequest for creating a request to a server handler. Go net/http/httptest documentation

Seed a resource with nonzero values before each case. That makes it possible to see whether omission preserves an existing value and whether a rejected update left state untouched.

Case Example body Assertions to make
Field omitted {} Check whether the existing value is preserved and assert the status required by the endpoint contract.
Explicit null {"name":null} Check whether null clears, removes, is rejected, or has another documented effect.
Valid replacement {"name":"Ada"} Assert success and the resulting value.
Wrong JSON type {"name":42} Assert the documented rejection or coercion policy; if rejected, verify state did not change.
Malformed JSON {"name": Assert the endpoint’s client-error response and unchanged state on rejection.
Domain-invalid value {"age":-1} Assert the validation response and unchanged state on rejection.
Unknown member {"typo":true} Assert the API’s documented reject-or-ignore policy.

These are useful test inputs, not universal expected status codes. Encode the endpoint’s actual response contract in each case, including any structured error body.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Example handler-test shape

func TestPatchHandler(t *testing.T) {
    tests := []struct {
        name       string
        body       string
        wantStatus int
        wantName   string
    }{
        // Populate expected status and final state from this API's contract.
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            store := newStoreWithResource("Grace")
            handler := newHandler(store)

            req := httptest.NewRequest(
                http.MethodPatch,
                "/users/1",
                strings.NewReader(tt.body),
            )
            req.Header.Set("Content-Type", "application/merge-patch+json")
            rec := httptest.NewRecorder()

            handler.ServeHTTP(rec, req)

            if rec.Code != tt.wantStatus {
                t.Fatalf("status = %d, want %d; body: %s", rec.Code, tt.wantStatus, rec.Body.String())
            }
            if got := store.Get("1").Name; got != tt.wantName {
                t.Fatalf("stored name = %q, want %q", got, tt.wantName)
            }
        })
    }
}

The example uses Merge Patch’s media type only to illustrate setting a request header. Substitute the actual format and expected status, response, and state for your endpoint. In production-quality tests, check the response body as well as the status and stored value.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Verify rejected patches do not partially apply

PATCH application is atomic under RFC 5789: a server must not expose a partially applied patch if the complete patch cannot be applied. For invalid input, inspect the resource after the request, not just the error response. A request that changes one field before failing validation on another should leave the resource in its original state. RFC 5789

For a reliable failure test, seed multiple fields, submit a patch that would alter one field but makes another invalid, then assert the error response and compare the complete resulting resource with the original. This catches update code that mutates a stored object incrementally before validation finishes.

Do not confuse JSON Merge Patch with JSON Patch

The word “PATCH” names the HTTP method; it does not determine whether null means removal. Choose tests based on the document format the endpoint accepts.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Format Media type How to interpret null and changes
JSON Merge Patch application/merge-patch+json Object members in the patch add or replace values; a member set to null removes that member from the target. A non-object patch replaces the whole target. This format is not suitable when explicit JSON null must be stored as a meaningful member value. RFC 7396
JSON Patch application/json-patch+json An ordered array of operations such as add, remove, replace, move, copy, and test. A null inside an operation’s value is data, not Merge Patch’s instruction to remove an object member. Include failing operations in tests and verify atomic application. RFC 6902

Merge Patch fits object-shaped additions, replacements, and removals. JSON Patch makes ordered operations explicit, which can be useful for array edits or when null must remain a value. The format choice affects client behavior and the tests you should write; it should be explicit in the API contract and media-type handling.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Shenzhen desk3 min
    HONOR Expands Beyond Smartphones With Humanoid Robot RevealHONOR said it unveiled its first humanoid robot at MWC 2026 and named shopping assistance, workplace inspections, and supportive companionship as intended uses. Later Robotics D1 claims and a reported…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.