Repository: Khan/genqlient
Pull request #117 (2021-09-28 03:33:18)

Add clearer error text a few places
## Summary:
It's nice to be clear here because it may be your first interaction with
genqlient!

## Test plan:
make check



Patch:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 5344d9c..f6ca18c 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -34,6 +34,7 @@ When releasing a new version:
 
 - The `omitempty` option now works correctly for struct- and map-typed variables, matching `encoding/json`, which is to say it never omits structs, and omits empty maps. (#43)
 - Generated type-names now abbreviate across multiple components; for example if the path to a type is `(MyOperation, Outer, Outer, Inner, OuterInner)`, it will again be called `MyOperationOuterInner`.  (This regressed in a pre-v0.1.0 refactor.) (#109)
+- Previously, interface fields with `# @genqlient(pointer: true)` would be unmarshaled to `(*MyInterface)(*<nil>)`, i.e. a pointer to the untyped-nil of the interface type.  Now they are unmarshaled as `(*MyInterface)(<nil>)`, i.e. a nil pointer of the pointer-to-interface type, as you would expect.
 
 ## v0.1.0
 
diff --git a/generate/config.go b/generate/config.go
index 5aa11ca..f23e668 100644
--- a/generate/config.go
+++ b/generate/config.go
@@ -117,8 +117,8 @@ func initConfig(filename string) error {
 	}
 	w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
 	if err != nil {
-		return err
+		return errorf(nil, "unable to write default genqlient.yaml: %v", err)
 	}
 	_, err = io.Copy(w, r)
-	return err
+	return errorf(nil, "unable to write default genqlient.yaml: %v", err)
 }
diff --git a/generate/generate.go b/generate/generate.go
index 5ca4ace..ecf2722 100644
--- a/generate/generate.go
+++ b/generate/generate.go
@@ -316,7 +316,8 @@ func Generate(config *Config) (map[string][]byte, error) {
 	// way.  (As-is, we generate a broken file, with just (unused) imports.)
 	if len(document.Operations) == 0 {
 		// Hard to have a position when there are no operations :(
-		return nil, errorf(nil, "no queries found, looked in: %v",
+		return nil, errorf(nil,
+			"no queries found, looked in: %v (configure this in genqlient.yaml)",
 			strings.Join(config.Operations, ", "))
 	}
 
diff --git a/generate/marshal.go.tmpl b/generate/marshal.go.tmpl
index 10d595e..0f31c3d 100644
--- a/generate/marshal.go.tmpl
+++ b/generate/marshal.go.tmpl
@@ -30,9 +30,15 @@ func (v *{{.GoName}}) MarshalJSON() ([]byte, error) {
         for i, src := range src {
             dst := &(*dst)[i]
         {{end -}}
+        {{/* src now has type <GoType>; dst is json.RawMessage */ -}}
+        {{if $field.GoType.IsPointer -}}
+        {{/* If you passed a pointer, and it's nil, don't call the
+             marshaler.  This matches json.Marshal's behavior. */ -}}
+        if src != nil {
+        {{end -}}
         var err error
         *dst, err = {{$field.Marshaler $.Generator}}(
-            {{/* src is a pointer to the struct-field (or field-element, etc.).
+            {{/* src is the struct-field (or field-element, etc.).
                  We want to pass a pointer to the type you specified, so if
                  there's a pointer on the field that's exactly what we want,
                  and if not we need to take the address. */ -}}
@@ -41,6 +47,9 @@ func (v *{{.GoName}}) MarshalJSON() ([]byte, error) {
             return nil, fmt.Errorf(
                 "Unable to marshal {{$.GoName}}.{{$field.GoName}}: %w", err)
         }
+        {{if $field.GoType.IsPointer -}}
+        }{{/* end if src != nil */}}
+        {{end -}}
         {{range $i := intRange $field.GoType.SliceDepth -}}
         }
         {{end -}}
diff --git a/generate/unmarshal.go.tmpl b/generate/unmarshal.go.tmpl
index 14a08b9..db0942b 100644
--- a/generate/unmarshal.go.tmpl
+++ b/generate/unmarshal.go.tmpl
@@ -102,17 +102,24 @@ func (v *{{.GoName}}) UnmarshalJSON(b []byte) error {
         for i, src := range src {
             dst := &(*dst)[i]
         {{end -}}
-        {{if $field.GoType.IsPointer -}}
-        {{/* In this case, the parent for loop did `make([]*MyType, ...)` and
-             we have a pointer into that list.  But we actually still need to
-             initialize the *elements* of the list. */ -}}
-        *dst = new({{$field.GoType.Unwrap.Reference}})
-        {{end -}}
-        err = {{$field.Unmarshaler $.Generator}}(
-            src, {{if $field.GoType.IsPointer}}*{{end}}dst)
-        if err != nil {
-            return fmt.Errorf(
-                "Unable to unmarshal {{$.GoName}}.{{$field.GoName}}: %w", err)
+        {{/* dst now has type *<GoType>; dst is json.RawMessage */ -}}
+        {{/* If the field is null in the input, skip calling unmarshaler.
+             (This matches json.Unmarshal.)  If the field is missing entirely
+             from the input, we will have an uninitialized json.RawMessage;
+             handle that likewise. */ -}}
+        if len(src) != 0 && string(src) != "null" {
+            {{if $field.GoType.IsPointer -}}
+            {{/* In this case, the parent for loop did `make([]*MyType, ...)`
+                 and we have a pointer into that list.  But we actually still
+                 need to initialize the *elements* of the list. */ -}}
+            *dst = new({{$field.GoType.Unwrap.Reference}})
+            {{end -}}
+            err = {{$field.Unmarshaler $.Generator}}(
+                src, {{if $field.GoType.IsPointer}}*{{end}}dst)
+            if err != nil {
+                return fmt.Errorf(
+                    "Unable to unmarshal {{$.GoName}}.{{$field.GoName}}: %w", err)
+            }
         }
         {{range $i := intRange $field.GoType.SliceDepth -}}
         }
diff --git a/internal/integration/generated.go b/internal/integration/generated.go
index 40ab3a1..d904fa7 100644
--- a/internal/integration/generated.go
+++ b/internal/integration/generated.go
@@ -40,11 +40,13 @@ func (v *AnimalFields) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.Owner
 		src := firstPass.Owner
-		err = __unmarshalAnimalFieldsOwnerBeing(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal AnimalFields.Owner: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalAnimalFieldsOwnerBeing(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal AnimalFields.Owner: %w", err)
+			}
 		}
 	}
 	return nil
@@ -314,6 +316,39 @@ func (v *__queryWithCustomMarshalInput) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&fullObject)
 }
 
+// __queryWithCustomMarshalOptionalInput is used internally by genqlient
+type __queryWithCustomMarshalOptionalInput struct {
+	Date *time.Time `json:"-"`
+	Id   *string    `json:"id"`
+}
+
+func (v *__queryWithCustomMarshalOptionalInput) MarshalJSON() ([]byte, error) {
+
+	var fullObject struct {
+		*__queryWithCustomMarshalOptionalInput
+		Date json.RawMessage `json:"date"`
+		graphql.NoMarshalJSON
+	}
+	fullObject.__queryWithCustomMarshalOptionalInput = v
+
+	{
+
+		dst := &fullObject.Date
+		src := v.Date
+		if src != nil {
+			var err error
+			*dst, err = testutil.MarshalDate(
+				src)
+			if err != nil {
+				return nil, fmt.Errorf(
+					"Unable to marshal __queryWithCustomMarshalOptionalInput.Date: %w", err)
+			}
+		}
+	}
+
+	return json.Marshal(&fullObject)
+}
+
 // __queryWithCustomMarshalSliceInput is used internally by genqlient
 type __queryWithCustomMarshalSliceInput struct {
 	Dates []time.Time `json:"-"`
@@ -396,6 +431,51 @@ type failingQueryResponse struct {
 	Me   failingQueryMeUser `json:"me"`
 }
 
+// queryWithCustomMarshalOptionalResponse is returned by queryWithCustomMarshalOptional on success.
+type queryWithCustomMarshalOptionalResponse struct {
+	UserSearch []queryWithCustomMarshalOptionalUserSearchUser `json:"userSearch"`
+}
+
+// queryWithCustomMarshalOptionalUserSearchUser includes the requested fields of the GraphQL type User.
+type queryWithCustomMarshalOptionalUserSearchUser struct {
+	Id        string    `json:"id"`
+	Name      string    `json:"name"`
+	Birthdate time.Time `json:"-"`
+}
+
+func (v *queryWithCustomMarshalOptionalUserSearchUser) UnmarshalJSON(b []byte) error {
+
+	if string(b) == "null" {
+		return nil
+	}
+
+	var firstPass struct {
+		*queryWithCustomMarshalOptionalUserSearchUser
+		Birthdate json.RawMessage `json:"birthdate"`
+		graphql.NoUnmarshalJSON
+	}
+	firstPass.queryWithCustomMarshalOptionalUserSearchUser = v
+
+	err := json.Unmarshal(b, &firstPass)
+	if err != nil {
+		return err
+	}
+
+	{
+		dst := &v.Birthdate
+		src := firstPass.Birthdate
+		if len(src) != 0 && string(src) != "null" {
+			err = testutil.UnmarshalDate(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal queryWithCustomMarshalOptionalUserSearchUser.Birthdate: %w", err)
+			}
+		}
+	}
+	return nil
+}
+
 // queryWithCustomMarshalResponse is returned by queryWithCustomMarshal on success.
 type queryWithCustomMarshalResponse struct {
 	UsersBornOn []queryWithCustomMarshalUsersBornOnUser `json:"usersBornOn"`
@@ -434,11 +514,13 @@ func (v *queryWithCustomMarshalSliceUsersBornOnDatesUser) UnmarshalJSON(b []byte
 	{
 		dst := &v.Birthdate
 		src := firstPass.Birthdate
-		err = testutil.UnmarshalDate(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal queryWithCustomMarshalSliceUsersBornOnDatesUser.Birthdate: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = testutil.UnmarshalDate(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal queryWithCustomMarshalSliceUsersBornOnDatesUser.Birthdate: %w", err)
+			}
 		}
 	}
 	return nil
@@ -472,11 +554,13 @@ func (v *queryWithCustomMarshalUsersBornOnUser) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.Birthdate
 		src := firstPass.Birthdate
-		err = testutil.UnmarshalDate(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal queryWithCustomMarshalUsersBornOnUser.Birthdate: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = testutil.UnmarshalDate(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal queryWithCustomMarshalUsersBornOnUser.Birthdate: %w", err)
+			}
 		}
 	}
 	return nil
@@ -513,11 +597,13 @@ func (v *queryWithFragmentsBeingsAnimal) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.Owner
 		src := firstPass.Owner
-		err = __unmarshalqueryWithFragmentsBeingsAnimalOwnerBeing(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal queryWithFragmentsBeingsAnimal.Owner: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalqueryWithFragmentsBeingsAnimalOwnerBeing(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal queryWithFragmentsBeingsAnimal.Owner: %w", err)
+			}
 		}
 	}
 	return nil
@@ -722,11 +808,13 @@ func (v *queryWithFragmentsResponse) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalqueryWithFragmentsBeingsBeing(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal queryWithFragmentsResponse.Beings: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalqueryWithFragmentsBeingsBeing(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal queryWithFragmentsResponse.Beings: %w", err)
+				}
 			}
 		}
 	}
@@ -846,11 +934,13 @@ func (v *queryWithInterfaceListFieldResponse) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalqueryWithInterfaceListFieldBeingsBeing(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal queryWithInterfaceListFieldResponse.Beings: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalqueryWithInterfaceListFieldBeingsBeing(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal queryWithInterfaceListFieldResponse.Beings: %w", err)
+				}
 			}
 		}
 	}
@@ -970,12 +1060,14 @@ func (v *queryWithInterfaceListPointerFieldResponse) UnmarshalJSON(b []byte) err
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			*dst = new(queryWithInterfaceListPointerFieldBeingsBeing)
-			err = __unmarshalqueryWithInterfaceListPointerFieldBeingsBeing(
-				src, *dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal queryWithInterfaceListPointerFieldResponse.Beings: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				*dst = new(queryWithInterfaceListPointerFieldBeingsBeing)
+				err = __unmarshalqueryWithInterfaceListPointerFieldBeingsBeing(
+					src, *dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal queryWithInterfaceListPointerFieldResponse.Beings: %w", err)
+				}
 			}
 		}
 	}
@@ -1097,11 +1189,13 @@ func (v *queryWithInterfaceNoFragmentsResponse) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.Being
 		src := firstPass.Being
-		err = __unmarshalqueryWithInterfaceNoFragmentsBeing(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal queryWithInterfaceNoFragmentsResponse.Being: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalqueryWithInterfaceNoFragmentsBeing(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal queryWithInterfaceNoFragmentsResponse.Being: %w", err)
+			}
 		}
 	}
 	return nil
@@ -1262,11 +1356,13 @@ func (v *queryWithNamedFragmentsResponse) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalqueryWithNamedFragmentsBeingsBeing(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal queryWithNamedFragmentsResponse.Beings: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalqueryWithNamedFragmentsBeingsBeing(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal queryWithNamedFragmentsResponse.Beings: %w", err)
+				}
 			}
 		}
 	}
@@ -1474,6 +1570,37 @@ query queryWithCustomMarshalSlice ($dates: [Date!]!) {
 	return &retval, err
 }
 
+func queryWithCustomMarshalOptional(
+	ctx context.Context,
+	client graphql.Client,
+	date *time.Time,
+	id *string,
+) (*queryWithCustomMarshalOptionalResponse, error) {
+	__input := __queryWithCustomMarshalOptionalInput{
+		Date: date,
+		Id:   id,
+	}
+	var err error
+
+	var retval queryWithCustomMarshalOptionalResponse
+	err = client.MakeRequest(
+		ctx,
+		"queryWithCustomMarshalOptional",
+		`
+query queryWithCustomMarshalOptional ($date: Date, $id: ID) {
+	userSearch(birthdate: $date, id: $id) {
+		id
+		name
+		birthdate
+	}
+}
+`,
+		&retval,
+		&__input,
+	)
+	return &retval, err
+}
+
 func queryWithInterfaceNoFragments(
 	ctx context.Context,
 	client graphql.Client,
diff --git a/internal/integration/schema.graphql b/internal/integration/schema.graphql
index e7c5942..a9e581c 100644
--- a/internal/integration/schema.graphql
+++ b/internal/integration/schema.graphql
@@ -8,6 +8,7 @@ type Query {
   lotteryWinner(number: Int!): Lucky
   usersBornOn(date: Date!): [User!]!
   usersBornOnDates(dates: [Date!]!): [User!]!
+  userSearch(birthdate: Date, id: ID): [User]
   fail: Boolean
 }
 
diff --git a/internal/integration/server/gqlgen_exec.go b/internal/integration/server/gqlgen_exec.go
index 7c7e8e3..6a4cf24 100644
--- a/internal/integration/server/gqlgen_exec.go
+++ b/internal/integration/server/gqlgen_exec.go
@@ -65,6 +65,7 @@ type ComplexityRoot struct {
 		LotteryWinner    func(childComplexity int, number int) int
 		Me               func(childComplexity int) int
 		User             func(childComplexity int, id *string) int
+		UserSearch       func(childComplexity int, birthdate *string, id *string) int
 		UsersBornOn      func(childComplexity int, date string) int
 		UsersBornOnDates func(childComplexity int, dates []string) int
 	}
@@ -86,6 +87,7 @@ type QueryResolver interface {
 	LotteryWinner(ctx context.Context, number int) (Lucky, error)
 	UsersBornOn(ctx context.Context, date string) ([]*User, error)
 	UsersBornOnDates(ctx context.Context, dates []string) ([]*User, error)
+	UserSearch(ctx context.Context, birthdate *string, id *string) ([]*User, error)
 	Fail(ctx context.Context) (*bool, error)
 }
 
@@ -215,6 +217,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Query.User(childComplexity, args["id"].(*string)), true
 
+	case "Query.userSearch":
+		if e.complexity.Query.UserSearch == nil {
+			break
+		}
+
+		args, err := ec.field_Query_userSearch_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Query.UserSearch(childComplexity, args["birthdate"].(*string), args["id"].(*string)), true
+
 	case "Query.usersBornOn":
 		if e.complexity.Query.UsersBornOn == nil {
 			break
@@ -334,6 +348,7 @@ type Query {
   lotteryWinner(number: Int!): Lucky
   usersBornOn(date: Date!): [User!]!
   usersBornOnDates(dates: [Date!]!): [User!]!
+  userSearch(birthdate: Date, id: ID): [User]
   fail: Boolean
 }
 
@@ -438,6 +453,30 @@ func (ec *executionContext) field_Query_lotteryWinner_args(ctx context.Context,
 	return args, nil
 }
 
+func (ec *executionContext) field_Query_userSearch_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := rawArgs["birthdate"]; ok {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("birthdate"))
+		arg0, err = ec.unmarshalODate2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["birthdate"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["id"]; ok {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
+		arg1, err = ec.unmarshalOID2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["id"] = arg1
+	return args, nil
+}
+
 func (ec *executionContext) field_Query_user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
 	var err error
 	args := map[string]interface{}{}
@@ -1032,6 +1071,45 @@ func (ec *executionContext) _Query_usersBornOnDates(ctx context.Context, field g
 	return ec.marshalNUser2ᚕᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUserᚄ(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _Query_userSearch(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:     "Query",
+		Field:      field,
+		Args:       nil,
+		IsMethod:   true,
+		IsResolver: true,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Query_userSearch_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	fc.Args = args
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Query().UserSearch(rctx, args["birthdate"].(*string), args["id"].(*string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.([]*User)
+	fc.Result = res
+	return ec.marshalOUser2ᚕᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _Query_fail(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 	defer func() {
 		if r := recover(); r != nil {
@@ -2628,6 +2706,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
 				}
 				return res
 			})
+		case "userSearch":
+			field := field
+			out.Concurrently(i, func() (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._Query_userSearch(ctx, field)
+				return res
+			})
 		case "fail":
 			field := field
 			out.Concurrently(i, func() (res graphql.Marshaler) {
@@ -3516,6 +3605,46 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as
 	return graphql.MarshalString(*v)
 }
 
+func (ec *executionContext) marshalOUser2ᚕᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx context.Context, sel ast.SelectionSet, v []*User) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalOUser2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+	return ret
+}
+
 func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx context.Context, sel ast.SelectionSet, v *User) graphql.Marshaler {
 	if v == nil {
 		return graphql.Null
diff --git a/internal/integration/server/server.go b/internal/integration/server/server.go
index 3883504..d0412b0 100644
--- a/internal/integration/server/server.go
+++ b/internal/integration/server/server.go
@@ -107,6 +107,17 @@ func (r *queryResolver) UsersBornOnDates(ctx context.Context, dates []string) ([
 	return usersByBirthdates(dates), nil
 }
 
+func (r *queryResolver) UserSearch(ctx context.Context, birthdate *string, id *string) ([]*User, error) {
+	switch {
+	case birthdate == nil && id != nil:
+		return []*User{userByID(*id)}, nil
+	case birthdate != nil && id == nil:
+		return usersByBirthdates([]string{*birthdate}), nil
+	default:
+		return nil, fmt.Errorf("need exactly one of birthdate or id")
+	}
+}
+
 func (r *queryResolver) Fail(ctx context.Context) (*bool, error) {
 	f := true
 	return &f, fmt.Errorf("oh no")


Test patch:
diff --git a/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go
index a5df875..5e4d035 100644
--- a/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-ComplexInlineFragments.graphql-ComplexInlineFragments.graphql.go
@@ -206,11 +206,13 @@ func (v *ComplexInlineFragmentsNestedStuffTopic) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalComplexInlineFragmentsNestedStuffTopicChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal ComplexInlineFragmentsNestedStuffTopic.Children: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalComplexInlineFragmentsNestedStuffTopicChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal ComplexInlineFragmentsNestedStuffTopic.Children: %w", err)
+				}
 			}
 		}
 	}
@@ -257,11 +259,13 @@ func (v *ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParen
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopic.Children: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopicChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal ComplexInlineFragmentsNestedStuffTopicChildrenArticleParentContentParentTopic.Children: %w", err)
+				}
 			}
 		}
 	}
@@ -819,44 +823,52 @@ func (v *ComplexInlineFragmentsResponse) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.RandomItem
 		src := firstPass.RandomItem
-		err = __unmarshalComplexInlineFragmentsRandomItemContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal ComplexInlineFragmentsResponse.RandomItem: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalComplexInlineFragmentsRandomItemContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal ComplexInlineFragmentsResponse.RandomItem: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.RepeatedStuff
 		src := firstPass.RepeatedStuff
-		err = __unmarshalComplexInlineFragmentsRepeatedStuffContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal ComplexInlineFragmentsResponse.RepeatedStuff: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalComplexInlineFragmentsRepeatedStuffContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal ComplexInlineFragmentsResponse.RepeatedStuff: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.ConflictingStuff
 		src := firstPass.ConflictingStuff
-		err = __unmarshalComplexInlineFragmentsConflictingStuffContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal ComplexInlineFragmentsResponse.ConflictingStuff: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalComplexInlineFragmentsConflictingStuffContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal ComplexInlineFragmentsResponse.ConflictingStuff: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.NestedStuff
 		src := firstPass.NestedStuff
-		err = __unmarshalComplexInlineFragmentsNestedStuffContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal ComplexInlineFragmentsResponse.NestedStuff: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalComplexInlineFragmentsNestedStuffContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal ComplexInlineFragmentsResponse.NestedStuff: %w", err)
+			}
 		}
 	}
 	return nil
diff --git a/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go
index ecd1254..df90dc5 100644
--- a/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-ComplexNamedFragments.graphql-ComplexNamedFragments.graphql.go
@@ -173,33 +173,39 @@ func (v *InnerQueryFragment) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.RandomItem
 		src := firstPass.RandomItem
-		err = __unmarshalInnerQueryFragmentRandomItemContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal InnerQueryFragment.RandomItem: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalInnerQueryFragmentRandomItemContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal InnerQueryFragment.RandomItem: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.RandomLeaf
 		src := firstPass.RandomLeaf
-		err = __unmarshalInnerQueryFragmentRandomLeafLeafContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal InnerQueryFragment.RandomLeaf: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalInnerQueryFragmentRandomLeafLeafContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal InnerQueryFragment.RandomLeaf: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.OtherLeaf
 		src := firstPass.OtherLeaf
-		err = __unmarshalInnerQueryFragmentOtherLeafLeafContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal InnerQueryFragment.OtherLeaf: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalInnerQueryFragmentOtherLeafLeafContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal InnerQueryFragment.OtherLeaf: %w", err)
+			}
 		}
 	}
 	return nil
@@ -703,11 +709,13 @@ func (v *MoreVideoFieldsParentTopic) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalMoreVideoFieldsParentTopicChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal MoreVideoFieldsParentTopic.Children: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalMoreVideoFieldsParentTopicChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal MoreVideoFieldsParentTopic.Children: %w", err)
+				}
 			}
 		}
 	}
diff --git a/generate/testdata/snapshots/TestGenerate-CustomMarshal.graphql-CustomMarshal.graphql.go b/generate/testdata/snapshots/TestGenerate-CustomMarshal.graphql-CustomMarshal.graphql.go
index 00c711d..bd7b140 100644
--- a/generate/testdata/snapshots/TestGenerate-CustomMarshal.graphql-CustomMarshal.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-CustomMarshal.graphql-CustomMarshal.graphql.go
@@ -49,11 +49,13 @@ func (v *CustomMarshalUsersBornOnUser) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.Birthdate
 		src := firstPass.Birthdate
-		err = testutil.UnmarshalDate(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal CustomMarshalUsersBornOnUser.Birthdate: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = testutil.UnmarshalDate(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal CustomMarshalUsersBornOnUser.Birthdate: %w", err)
+			}
 		}
 	}
 	return nil
diff --git a/generate/testdata/snapshots/TestGenerate-CustomMarshalSlice.graphql-CustomMarshalSlice.graphql.go b/generate/testdata/snapshots/TestGenerate-CustomMarshalSlice.graphql-CustomMarshalSlice.graphql.go
index d7462d3..dcc2c9e 100644
--- a/generate/testdata/snapshots/TestGenerate-CustomMarshalSlice.graphql-CustomMarshalSlice.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-CustomMarshalSlice.graphql-CustomMarshalSlice.graphql.go
@@ -82,12 +82,14 @@ func (v *__CustomMarshalSliceInput) MarshalJSON() ([]byte, error) {
 					len(src))
 				for i, src := range src {
 					dst := &(*dst)[i]
-					var err error
-					*dst, err = testutil.MarshalDate(
-						src)
-					if err != nil {
-						return nil, fmt.Errorf(
-							"Unable to marshal __CustomMarshalSliceInput.Datesssp: %w", err)
+					if src != nil {
+						var err error
+						*dst, err = testutil.MarshalDate(
+							src)
+						if err != nil {
+							return nil, fmt.Errorf(
+								"Unable to marshal __CustomMarshalSliceInput.Datesssp: %w", err)
+						}
 					}
 				}
 			}
diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go
index 351abae..11f475b 100644
--- a/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-InterfaceListField.graphql-InterfaceListField.graphql.go
@@ -50,11 +50,13 @@ func (v *InterfaceListFieldRootTopic) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalInterfaceListFieldRootTopicChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal InterfaceListFieldRootTopic.Children: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalInterfaceListFieldRootTopicChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal InterfaceListFieldRootTopic.Children: %w", err)
+				}
 			}
 		}
 	}
@@ -209,11 +211,13 @@ func (v *InterfaceListFieldWithPointerTopic) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalInterfaceListFieldWithPointerTopicChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal InterfaceListFieldWithPointerTopic.Children: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalInterfaceListFieldWithPointerTopicChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal InterfaceListFieldWithPointerTopic.Children: %w", err)
+				}
 			}
 		}
 	}
diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go
index 9cc43b1..f5b5092 100644
--- a/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-InterfaceListOfListsOfListsField.graphql-InterfaceListOfListsOfListsField.graphql.go
@@ -185,11 +185,13 @@ func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error
 					len(src))
 				for i, src := range src {
 					dst := &(*dst)[i]
-					err = __unmarshalInterfaceListOfListOfListsFieldListOfListsOfListsOfContent(
-						src, dst)
-					if err != nil {
-						return fmt.Errorf(
-							"Unable to unmarshal InterfaceListOfListOfListsFieldResponse.ListOfListsOfListsOfContent: %w", err)
+					if len(src) != 0 && string(src) != "null" {
+						err = __unmarshalInterfaceListOfListOfListsFieldListOfListsOfListsOfContent(
+							src, dst)
+						if err != nil {
+							return fmt.Errorf(
+								"Unable to unmarshal InterfaceListOfListOfListsFieldResponse.ListOfListsOfListsOfContent: %w", err)
+						}
 					}
 				}
 			}
@@ -214,12 +216,14 @@ func (v *InterfaceListOfListOfListsFieldResponse) UnmarshalJSON(b []byte) error
 					len(src))
 				for i, src := range src {
 					dst := &(*dst)[i]
-					*dst = new(InterfaceListOfListOfListsFieldWithPointerContent)
-					err = __unmarshalInterfaceListOfListOfListsFieldWithPointerContent(
-						src, *dst)
-					if err != nil {
-						return fmt.Errorf(
-							"Unable to unmarshal InterfaceListOfListOfListsFieldResponse.WithPointer: %w", err)
+					if len(src) != 0 && string(src) != "null" {
+						*dst = new(InterfaceListOfListOfListsFieldWithPointerContent)
+						err = __unmarshalInterfaceListOfListOfListsFieldWithPointerContent(
+							src, *dst)
+						if err != nil {
+							return fmt.Errorf(
+								"Unable to unmarshal InterfaceListOfListOfListsFieldResponse.WithPointer: %w", err)
+						}
 					}
 				}
 			}
diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go
index a493d8c..5a3f66e 100644
--- a/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-InterfaceNesting.graphql-InterfaceNesting.graphql.go
@@ -48,11 +48,13 @@ func (v *InterfaceNestingRootTopic) UnmarshalJSON(b []byte) error {
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalInterfaceNestingRootTopicChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal InterfaceNestingRootTopic.Children: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalInterfaceNestingRootTopicChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal InterfaceNestingRootTopic.Children: %w", err)
+				}
 			}
 		}
 	}
@@ -196,11 +198,13 @@ func (v *InterfaceNestingRootTopicChildrenContentParentTopic) UnmarshalJSON(b []
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalInterfaceNestingRootTopicChildrenContentParentTopicChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal InterfaceNestingRootTopicChildrenContentParentTopic.Children: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalInterfaceNestingRootTopicChildrenContentParentTopicChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal InterfaceNestingRootTopicChildrenContentParentTopic.Children: %w", err)
+				}
 			}
 		}
 	}
diff --git a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go
index b50b156..74b72c5 100644
--- a/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-InterfaceNoFragments.graphql-InterfaceNoFragments.graphql.go
@@ -275,34 +275,40 @@ func (v *InterfaceNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.RandomItem
 		src := firstPass.RandomItem
-		err = __unmarshalInterfaceNoFragmentsQueryRandomItemContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal InterfaceNoFragmentsQueryResponse.RandomItem: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalInterfaceNoFragmentsQueryRandomItemContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal InterfaceNoFragmentsQueryResponse.RandomItem: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.RandomItemWithTypeName
 		src := firstPass.RandomItemWithTypeName
-		err = __unmarshalInterfaceNoFragmentsQueryRandomItemWithTypeNameContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal InterfaceNoFragmentsQueryResponse.RandomItemWithTypeName: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalInterfaceNoFragmentsQueryRandomItemWithTypeNameContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal InterfaceNoFragmentsQueryResponse.RandomItemWithTypeName: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.WithPointer
 		src := firstPass.WithPointer
-		*dst = new(InterfaceNoFragmentsQueryWithPointerContent)
-		err = __unmarshalInterfaceNoFragmentsQueryWithPointerContent(
-			src, *dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal InterfaceNoFragmentsQueryResponse.WithPointer: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			*dst = new(InterfaceNoFragmentsQueryWithPointerContent)
+			err = __unmarshalInterfaceNoFragmentsQueryWithPointerContent(
+				src, *dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal InterfaceNoFragmentsQueryResponse.WithPointer: %w", err)
+			}
 		}
 	}
 	return nil
diff --git a/generate/testdata/snapshots/TestGenerate-MultipleDirectives.graphql-MultipleDirectives.graphql.go b/generate/testdata/snapshots/TestGenerate-MultipleDirectives.graphql-MultipleDirectives.graphql.go
index d17af64..560cb61 100644
--- a/generate/testdata/snapshots/TestGenerate-MultipleDirectives.graphql-MultipleDirectives.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-MultipleDirectives.graphql-MultipleDirectives.graphql.go
@@ -40,12 +40,14 @@ func (v *MyInput) MarshalJSON() ([]byte, error) {
 
 		dst := &fullObject.Birthdate
 		src := v.Birthdate
-		var err error
-		*dst, err = testutil.MarshalDate(
-			src)
-		if err != nil {
-			return nil, fmt.Errorf(
-				"Unable to marshal MyInput.Birthdate: %w", err)
+		if src != nil {
+			var err error
+			*dst, err = testutil.MarshalDate(
+				src)
+			if err != nil {
+				return nil, fmt.Errorf(
+					"Unable to marshal MyInput.Birthdate: %w", err)
+			}
 		}
 	}
 
@@ -127,12 +129,14 @@ func (v *UserQueryInput) MarshalJSON() ([]byte, error) {
 
 		dst := &fullObject.Birthdate
 		src := v.Birthdate
-		var err error
-		*dst, err = testutil.MarshalDate(
-			src)
-		if err != nil {
-			return nil, fmt.Errorf(
-				"Unable to marshal UserQueryInput.Birthdate: %w", err)
+		if src != nil {
+			var err error
+			*dst, err = testutil.MarshalDate(
+				src)
+			if err != nil {
+				return nil, fmt.Errorf(
+					"Unable to marshal UserQueryInput.Birthdate: %w", err)
+			}
 		}
 	}
 
diff --git a/generate/testdata/snapshots/TestGenerate-Pointers.graphql-Pointers.graphql.go b/generate/testdata/snapshots/TestGenerate-Pointers.graphql-Pointers.graphql.go
index 6b6a6f9..0c0cdae 100644
--- a/generate/testdata/snapshots/TestGenerate-Pointers.graphql-Pointers.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-Pointers.graphql-Pointers.graphql.go
@@ -95,12 +95,14 @@ func (v *UserQueryInput) MarshalJSON() ([]byte, error) {
 
 		dst := &fullObject.Birthdate
 		src := v.Birthdate
-		var err error
-		*dst, err = testutil.MarshalDate(
-			src)
-		if err != nil {
-			return nil, fmt.Errorf(
-				"Unable to marshal UserQueryInput.Birthdate: %w", err)
+		if src != nil {
+			var err error
+			*dst, err = testutil.MarshalDate(
+				src)
+			if err != nil {
+				return nil, fmt.Errorf(
+					"Unable to marshal UserQueryInput.Birthdate: %w", err)
+			}
 		}
 	}
 
diff --git a/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go
index 74f4cfd..673f55e 100644
--- a/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-SimpleInlineFragment.graphql-SimpleInlineFragment.graphql.go
@@ -152,11 +152,13 @@ func (v *SimpleInlineFragmentResponse) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.RandomItem
 		src := firstPass.RandomItem
-		err = __unmarshalSimpleInlineFragmentRandomItemContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal SimpleInlineFragmentResponse.RandomItem: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalSimpleInlineFragmentRandomItemContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal SimpleInlineFragmentResponse.RandomItem: %w", err)
+			}
 		}
 	}
 	return nil
diff --git a/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go b/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go
index f8e1583..b8d4de3 100644
--- a/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-SimpleNamedFragment.graphql-SimpleNamedFragment.graphql.go
@@ -269,22 +269,26 @@ func (v *SimpleNamedFragmentResponse) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.RandomItem
 		src := firstPass.RandomItem
-		err = __unmarshalSimpleNamedFragmentRandomItemContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal SimpleNamedFragmentResponse.RandomItem: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalSimpleNamedFragmentRandomItemContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal SimpleNamedFragmentResponse.RandomItem: %w", err)
+			}
 		}
 	}
 
 	{
 		dst := &v.RandomLeaf
 		src := firstPass.RandomLeaf
-		err = __unmarshalSimpleNamedFragmentRandomLeafLeafContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal SimpleNamedFragmentResponse.RandomLeaf: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalSimpleNamedFragmentRandomLeafLeafContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal SimpleNamedFragmentResponse.RandomLeaf: %w", err)
+			}
 		}
 	}
 	return nil
diff --git a/generate/testdata/snapshots/TestGenerate-StructOption.graphql-StructOption.graphql.go b/generate/testdata/snapshots/TestGenerate-StructOption.graphql-StructOption.graphql.go
index 7c6b631..29d8b47 100644
--- a/generate/testdata/snapshots/TestGenerate-StructOption.graphql-StructOption.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-StructOption.graphql-StructOption.graphql.go
@@ -86,11 +86,13 @@ func (v *StructOptionRootTopicChildrenContentParentTopic) UnmarshalJSON(b []byte
 			len(src))
 		for i, src := range src {
 			dst := &(*dst)[i]
-			err = __unmarshalStructOptionRootTopicChildrenContentParentTopicInterfaceChildrenContent(
-				src, dst)
-			if err != nil {
-				return fmt.Errorf(
-					"Unable to unmarshal StructOptionRootTopicChildrenContentParentTopic.InterfaceChildren: %w", err)
+			if len(src) != 0 && string(src) != "null" {
+				err = __unmarshalStructOptionRootTopicChildrenContentParentTopicInterfaceChildrenContent(
+					src, dst)
+				if err != nil {
+					return fmt.Errorf(
+						"Unable to unmarshal StructOptionRootTopicChildrenContentParentTopic.InterfaceChildren: %w", err)
+				}
 			}
 		}
 	}
diff --git a/generate/testdata/snapshots/TestGenerate-TypeNames.graphql-TypeNames.graphql.go b/generate/testdata/snapshots/TestGenerate-TypeNames.graphql-TypeNames.graphql.go
index fabe85f..cf8687b 100644
--- a/generate/testdata/snapshots/TestGenerate-TypeNames.graphql-TypeNames.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-TypeNames.graphql-TypeNames.graphql.go
@@ -153,11 +153,13 @@ func (v *Resp) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.RandomItem
 		src := firstPass.RandomItem
-		err = __unmarshalItem(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal Resp.RandomItem: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalItem(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal Resp.RandomItem: %w", err)
+			}
 		}
 	}
 	return nil
diff --git a/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go b/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go
index 657fe48..8f6d50e 100644
--- a/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go
+++ b/generate/testdata/snapshots/TestGenerate-UnionNoFragments.graphql-UnionNoFragments.graphql.go
@@ -100,11 +100,13 @@ func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
 	{
 		dst := &v.RandomLeaf
 		src := firstPass.RandomLeaf
-		err = __unmarshalUnionNoFragmentsQueryRandomLeafLeafContent(
-			src, dst)
-		if err != nil {
-			return fmt.Errorf(
-				"Unable to unmarshal UnionNoFragmentsQueryResponse.RandomLeaf: %w", err)
+		if len(src) != 0 && string(src) != "null" {
+			err = __unmarshalUnionNoFragmentsQueryRandomLeafLeafContent(
+				src, dst)
+			if err != nil {
+				return fmt.Errorf(
+					"Unable to unmarshal UnionNoFragmentsQueryResponse.RandomLeaf: %w", err)
+			}
 		}
 	}
 	return nil
diff --git a/generate/testdata/snapshots/TestGenerateErrors-NoQuery-go b/generate/testdata/snapshots/TestGenerateErrors-NoQuery-go
index 31df7bf..8527255 100644
--- a/generate/testdata/snapshots/TestGenerateErrors-NoQuery-go
+++ b/generate/testdata/snapshots/TestGenerateErrors-NoQuery-go
@@ -1,1 +1,1 @@
-no queries found, looked in: testdata/errors/NoQuery.go
+no queries found, looked in: testdata/errors/NoQuery.go (configure this in genqlient.yaml)
diff --git a/generate/testdata/snapshots/TestGenerateErrors-NoQuery-graphql b/generate/testdata/snapshots/TestGenerateErrors-NoQuery-graphql
index fe31004..45b85de 100644
--- a/generate/testdata/snapshots/TestGenerateErrors-NoQuery-graphql
+++ b/generate/testdata/snapshots/TestGenerateErrors-NoQuery-graphql
@@ -1,1 +1,1 @@
-no queries found, looked in: testdata/errors/NoQuery.graphql
+no queries found, looked in: testdata/errors/NoQuery.graphql (configure this in genqlient.yaml)
diff --git a/internal/integration/integration_test.go b/internal/integration/integration_test.go
index 41b1dde..132582a 100644
--- a/internal/integration/integration_test.go
+++ b/internal/integration/integration_test.go
@@ -175,6 +175,44 @@ func TestCustomMarshalSlice(t *testing.T) {
 	assert.Len(t, resp.UsersBornOnDates, 0)
 }
 
+func TestCustomMarshalOptional(t *testing.T) {
+	_ = `# @genqlient
+	query queryWithCustomMarshalOptional(
+		# @genqlient(pointer: true)
+		$date: Date,
+		# @genqlient(pointer: true)
+		$id: ID,
+	) {
+		userSearch(birthdate: $date, id: $id) { id name birthdate }
+	}`
+
+	ctx := context.Background()
+	server := server.RunServer()
+	defer server.Close()
+	client := graphql.NewClient(server.URL, http.DefaultClient)
+
+	date := time.Date(2025, time.January, 1, 12, 34, 56, 789, time.UTC)
+	resp, err := queryWithCustomMarshalOptional(ctx, client, &date, nil)
+	require.NoError(t, err)
+
+	assert.Len(t, resp.UserSearch, 1)
+	user := resp.UserSearch[0]
+	assert.Equal(t, "1", user.Id)
+	assert.Equal(t, "Yours Truly", user.Name)
+	assert.Equal(t,
+		time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
+		user.Birthdate)
+
+	id := "2"
+	resp, err = queryWithCustomMarshalOptional(ctx, client, nil, &id)
+	require.NoError(t, err)
+	assert.Len(t, resp.UserSearch, 1)
+	user = resp.UserSearch[0]
+	assert.Equal(t, "2", user.Id)
+	assert.Equal(t, "Raven", user.Name)
+	assert.Zero(t, user.Birthdate)
+}
+
 func TestInterfaceNoFragments(t *testing.T) {
 	_ = `# @genqlient
 	query queryWithInterfaceNoFragments($id: ID!) {
@@ -322,7 +360,7 @@ func TestInterfaceListPointerField(t *testing.T) {
 	assert.Equal(t, "3", animal.Id)
 	assert.Equal(t, "Fido", animal.Name)
 
-	assert.Nil(t, *resp.Beings[2])
+	assert.Nil(t, resp.Beings[2])
 }
 
 func TestFragments(t *testing.T) {
diff --git a/internal/testutil/types.go b/internal/testutil/types.go
index 9d5243c..e160a12 100644
--- a/internal/testutil/types.go
+++ b/internal/testutil/types.go
@@ -36,10 +36,6 @@ func MarshalDate(t *time.Time) ([]byte, error) {
 
 func UnmarshalDate(b []byte, t *time.Time) error {
 	// (modified from time.Time.UnmarshalJSON)
-	if string(b) == "null" {
-		return nil
-	}
-
 	var err error
 	*t, err = time.Parse(`"`+dateFormat+`"`, string(b))
 	return err