Skip to content

Commit d5384a2

Browse files
authored
Documentation of the Omittable type (#3813)
1 parent 5de03f4 commit d5384a2

File tree

1 file changed

+89
-2
lines changed

1 file changed

+89
-2
lines changed

docs/content/reference/changesets.md

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ description: Falling back to map[string]interface{} to allow for presence checks
55
menu: { main: { parent: 'reference', weight: 10 } }
66
---
77

8-
Occasionally you need to distinguish presence from nil (undefined vs null). In gqlgen we do this using maps:
8+
Occasionally you need to distinguish presence from nil (undefined vs null). In gqlgen this can be done using either maps or the Omittable type.
99

10+
## Maps
1011

1112
```graphql
1213
type Mutation {
@@ -30,7 +31,29 @@ After running go generate you should end up with a resolver that looks like this
3031
```go
3132
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, changes map[string]interface{}) (*User, error) {
3233
u := fetchFromDb(id)
33-
/// apply the changes
34+
35+
// Check if name was provided in the input
36+
if v, isSet := changes["name"]; isSet { // v is the value with type `interface{}`
37+
value, valid := v.(*string) // *string, could be nil
38+
if !valid {
39+
// map values are automatically coerced to the types defined in the schema,
40+
// so if this error is thrown it's most likely a type mismatch between here and your GraphQL input definition
41+
return nil, errors.New("field 'name' on UserChanges does not have type String")
42+
}
43+
44+
if value == nil {
45+
u.Name = "" // value to use when null
46+
} else {
47+
u.Name = *value // set to the provided value
48+
}
49+
}
50+
// If !isSet, the field was omitted entirely - no change
51+
52+
// Alternative: use reflection (see below)
53+
if err := ApplyChanges(changes, &u); err != nil {
54+
return nil, err
55+
}
56+
3457
saveToDb(u)
3558
return u, nil
3659
}
@@ -68,3 +91,67 @@ func ApplyChanges(changes map[string]interface{}, to interface{}) error {
6891
return dec.Decode(changes)
6992
}
7093
```
94+
95+
## Omittable
96+
97+
The `Omittable[T]` type provides a more type-safe alternative to maps for distinguishing between unset, null, and actual values. It's a generic wrapper that tracks both the value and whether it was explicitly provided.
98+
99+
You can enable omittable fields in two ways:
100+
101+
**Option 1: Per-field with directive**
102+
```graphql
103+
input UserChanges {
104+
name: String @goField(omittable: true)
105+
email: String @goField(omittable: true)
106+
}
107+
```
108+
109+
**Option 2: Globally in config**
110+
```yaml
111+
# gqlgen.yml
112+
nullable_input_omittable: true
113+
```
114+
115+
This generates a Go struct using `graphql.Omittable`:
116+
117+
```go
118+
type UserChanges struct {
119+
Name graphql.Omittable[*string] `json:"name,omitempty"`
120+
Email graphql.Omittable[*string] `json:"email,omitempty"`
121+
}
122+
```
123+
124+
Your resolver can then distinguish between three states:
125+
126+
```go
127+
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, changes UserChanges) (*User, error) {
128+
u := fetchFromDb(id)
129+
130+
// Check if name was provided in the input
131+
if changes.Name.IsSet() {
132+
value := changes.Name.Value() // *string, could be nil
133+
if value == nil {
134+
u.Name = "" // value to use when null
135+
} else {
136+
u.Name = *value // set to the provided value
137+
}
138+
}
139+
// If !changes.Name.IsSet(), the field was omitted entirely - no change
140+
141+
// Alternative: use ValueOK for cleaner code
142+
if value, isSet := changes.Email.ValueOK(); isSet {
143+
u.Email = value // *string, nil if null was provided, actual value otherwise
144+
}
145+
146+
saveToDb(u)
147+
return u, nil
148+
}
149+
```
150+
151+
### Key Methods
152+
153+
- `IsSet()` - Returns true if the field was explicitly provided (even if null)
154+
- `Value()` - Returns the value, or zero value if not set
155+
- `ValueOK()` - Returns (value, wasSet) similar to map access
156+
- `OmittableOf(value)` - Helper to create an Omittable with a value
157+

0 commit comments

Comments
 (0)