Skip to content

fix(members): let a populated custom field be cleared again - #68

Merged
SiteRelEnby merged 1 commit into
mainfrom
fix/clear-custom-field-value
Sep 1, 2026
Merged

fix(members): let a populated custom field be cleared again#68
SiteRelEnby merged 1 commit into
mainfrom
fix/clear-custom-field-value

Conversation

@SiteRelEnby

@SiteRelEnby SiteRelEnby commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Emptying a custom field on a member did nothing: the save failed and the old value stayed.

It's the client, not the backend

Clearing is expressed as value: null on PUT /v1/members/{id}/fields. The server upserts that null rather than deleting the row, and value is required on every entry - there is no "omit to leave this one alone" mode.

Moshi omits null fields by default, so emptying a text field (which stages null via it.ifBlank { null }) serialised to:

[{"field_id": "..."}]

No value key at all. That isn't a weaker request, it's an invalid one.

Both halves verified rather than reasoned about:

  • Serialisation - probed the adapter directly: PROBE-CLEAR: [{"field_id":"f1"}] vs PROBE-SET: [{"field_id":"f1","value":"hi"}].
  • Rejection - against the server's own Pydantic 2.12.5: an omitted value raises a ValidationError, an explicit null is accepted and stores as null.

The fix

A hand-written adapter that always writes value, null included. This is the same trap the fronts PATCH hit, approached from the other side: FrontUpdateJsonAdapter exists to let an explicit null through, this one exists to stop one being dropped.

Registered via a factory, since the adapter needs the Moshi instance to delegate type-erased values (a field value can be a string, number, boolean, or a list for multiselect).

Tests

7, covering the cleared entry, a set entry, non-string types, and a mixed batch that clears one field while setting another in the same save - which is what a real edit looks like.

One test deliberately builds the app's own Moshi via NetworkModule.provideMoshi() rather than a local one. Without it, forgetting the registration would leave every other test passing while clearing stayed broken in the app.

Also worth doing on the server, separately

Accepting a missing value as null (value: Any = None) would protect any other client whose serialiser omits nulls - Moshi does, Go's omitempty does, various JS paths do. It costs nothing, since absence has no other meaning on this endpoint.

It is not a substitute for this fix: a server-side change only helps once an instance upgrades, and self-hosters move on their own schedule. Same reasoning as the replace-endpoint fallback in #59. Filing that separately against the server repo.

Testing

:app:assemblePlayRelease + :app:testPlayReleaseUnitTest green. Device checklist:

  • Populate a text custom field on a member, save, reopen, clear it, save: it stays cleared
  • Same for a number, a date, and a boolean field
  • Multiselect: clear all selections and save
  • Clear one field while editing another in the same save: both take effect
  • The cleared field shows as unset on the member's profile, not as a stale value

Emptying a custom field on a member did nothing: the save failed and the old
value stayed.

Clearing is expressed as `value: null` on PUT /v1/members/{id}/fields, which
the server upserts as a null rather than deleting the row. `value` is required
on every entry; there is no "omit to leave this one alone" mode. Moshi omits
null fields by default, so a clear serialised to

  [{"field_id": "..."}]

with no `value` at all. That is not a smaller request, it is an invalid one,
and the server rejected it. Verified both halves: the serialisation by probing
the adapter, and the rejection against the server's own Pydantic, which raises
a validation error for a missing `value` and accepts an explicit null.

Adds a hand-written adapter that always writes `value`, null included. Same
trap the fronts PATCH hit, from the other side: there the fix was to let an
explicit null through, here it is to stop dropping one.

Tests cover the cleared entry, a set entry, non-string value types, and a
mixed batch that clears one field while setting another in the same save. One
of them builds the app's own Moshi rather than a local one, so forgetting the
registration fails a test instead of quietly restoring the bug.

Worth doing on the server too, as defence for any other client whose serialiser
omits nulls: accepting a missing `value` as null costs nothing, since the
endpoint has no other meaning for its absence. That is not a substitute for
this, though, since it would only help once an instance upgrades.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant