Summary
We've been using mold in production and really appreciate the library — it handles the vast majority of our transform needs cleanly. This is a feature suggestion that would make it even more powerful for multi-step pipelines.
Currently, Transformer.Struct() runs all registered transforms in a single pass. It would be great to have a way to selectively run only certain transforms (by tag name) in a given call — enabling a natural "pre-parse" and "post-parse" transform pattern with a single struct definition.
Background: the multi-phase transform use case
A typical HTTP handler pipeline looks like this:
json.Unmarshal(body, &dto) // parse
transformer.Struct(&dto) // transform (trim, lcase, default=X, ...)
validator.Struct(&dto) // validate
This works well for most transforms (trim, lcase, ucase, etc.) since they operate on values the client explicitly sent. However, it can make default=X tricky for pointer types and for fields where the Go zero-value is a valid client-supplied value.
Why default=X is hard to use correctly after parsing
Consider this struct:
type UpdateRequest struct {
Source *string `json:"source" mold:"default=web"`
Visible bool `json:"visible" mold:"default=true"`
Limit int `json:"limit" mold:"default=10"`
}
| Client sends |
Field after parse |
After default=X |
Intended? |
| key absent |
nil / false / 0 |
"web" / true / 10 |
✅ correct |
"source": "mobile" |
*"mobile" |
*"mobile" |
✅ correct |
"source": null |
nil |
"web" ← overwritten |
❌ not ideal |
"visible": false |
false |
true ← overwritten |
❌ not ideal |
"limit": 0 |
0 |
10 ← overwritten |
❌ not ideal |
The challenge: after parsing, a field explicitly set to null, false, or 0 by the client looks identical to a field that was never sent — both are Go zero values. So default=X has no way to distinguish them and ends up overwriting deliberate client values.
This makes it difficult to correctly support opt-in boolean fields, nullable pointer fields, or zero-as-valid-value fields using default=X in a post-parse transform pass.
A nicer order: default → parse → transform → validate
Running default=X before parsing on a zero-value struct neatly solves this:
transformer.Struct(&dto) // phase 1: apply "default=X" to zero-value struct
json.Unmarshal(body, &dto) // parse: client values overwrite defaults for present keys
transformer.Struct(&dto) // phase 2: trim, lcase, etc. on decoded values
validator.Struct(&dto) // validate
Fields absent from the JSON payload keep the default (unmarshalling leaves them untouched), while fields explicitly sent (even null, false, 0) have the parser overwrite the default, so the client always wins.
| Client sends |
After phase-1 default |
After parse |
After phase-2 trim/lcase |
Intended? |
| key absent |
"web" |
"web" (untouched) |
"web" |
✅ |
"source": "mobile" |
"web" |
"mobile" |
"mobile" |
✅ |
"source": null |
"web" |
nil |
nil |
✅ client explicitly cleared it |
"visible": false |
true |
false |
false |
✅ client explicitly set false |
"limit": 0 |
10 |
0 |
0 |
✅ client explicitly set zero |
Suggested API
Any of the following approaches would cover this use case nicely:
Option A — filter execution by tag name (minimal change, most immediately useful)
// Run only transforms whose modifier matches the given names
transformer.StructFiltered(ctx, &dto, "default")
json.Unmarshal(body, &dto)
// Run all transforms except "default"
transformer.StructExcluding(ctx, &dto, "default")
Option B — factory for tag-scoped Transformer instances
// A cleaner alternative to manually maintaining two instances via SetTagName:
preTransformer := mold.NewForTags("default")
postTransformer := mold.NewExcludingTags("default")
Option C — struct-level tag grouping (most expressive)
type UpdateRequest struct {
Source *string `json:"source" mold_pre:"default=web" mold_post:"trim"`
}
// mold_pre runs before parse, mold_post runs after parse
We think Option A would be the easiest to adopt and already covers the common case well.
Current workaround
We currently handle this by creating two Transformer instances with different SetTagName values (pretransform and transform) and annotating structs with two separate tags. It works, but it means every struct author needs to be aware of the two-phase pipeline, and transform registrations have to be duplicated. A first-class API for this would be a much cleaner solution.
Environment
github.com/go-playground/mold/v4 v4.5.1
-
Summary
We've been using
moldin production and really appreciate the library — it handles the vast majority of our transform needs cleanly. This is a feature suggestion that would make it even more powerful for multi-step pipelines.Currently,
Transformer.Struct()runs all registered transforms in a single pass. It would be great to have a way to selectively run only certain transforms (by tag name) in a given call — enabling a natural "pre-parse" and "post-parse" transform pattern with a single struct definition.Background: the multi-phase transform use case
A typical HTTP handler pipeline looks like this:
This works well for most transforms (
trim,lcase,ucase, etc.) since they operate on values the client explicitly sent. However, it can makedefault=Xtricky for pointer types and for fields where the Go zero-value is a valid client-supplied value.Why
default=Xis hard to use correctly after parsingConsider this struct:
default=Xnil/false/0"web"/true/10"source": "mobile"*"mobile"*"mobile""source": nullnil"web"← overwritten"visible": falsefalsetrue← overwritten"limit": 0010← overwrittenThe challenge: after parsing, a field explicitly set to
null,false, or0by the client looks identical to a field that was never sent — both are Go zero values. Sodefault=Xhas no way to distinguish them and ends up overwriting deliberate client values.This makes it difficult to correctly support opt-in boolean fields, nullable pointer fields, or zero-as-valid-value fields using
default=Xin a post-parse transform pass.A nicer order: default → parse → transform → validate
Running
default=Xbefore parsing on a zero-value struct neatly solves this:Fields absent from the JSON payload keep the default (unmarshalling leaves them untouched), while fields explicitly sent (even
null,false,0) have the parser overwrite the default, so the client always wins."web""web"(untouched)"web""source": "mobile""web""mobile""mobile""source": null"web"nilnil"visible": falsetruefalsefalse"limit": 01000Suggested API
Any of the following approaches would cover this use case nicely:
Option A — filter execution by tag name (minimal change, most immediately useful)
Option B — factory for tag-scoped Transformer instances
Option C — struct-level tag grouping (most expressive)
We think Option A would be the easiest to adopt and already covers the common case well.
Current workaround
We currently handle this by creating two
Transformerinstances with differentSetTagNamevalues (pretransformandtransform) and annotating structs with two separate tags. It works, but it means every struct author needs to be aware of the two-phase pipeline, and transform registrations have to be duplicated. A first-class API for this would be a much cleaner solution.Environment
github.com/go-playground/mold/v4 v4.5.1