github.com/codykaup/genqlient@v0.6.2/generate/unmarshal.go.tmpl (about) 1 {{/* We need to generate UnmarshalJSON methods for some types that we want to 2 handle specially. Specifically, we generate an UnmarshalJSON for each 3 struct with a field meeting any of these criteria: 4 - a field whose type is configured with a custom unmarshaler 5 - an embedded (anonymous) field; technically we only need an 6 UnmarshalJSON if the embedded type needs one, but it's easier to 7 generate it unconditionally 8 - a field of interface type 9 Additionally, since we add `json:"-"` to fields we handle specially, for 10 any field which requires a MarshalJSON, we also generate an UnmarshalJSON, 11 and vice versa. 12 13 Given that, we want to specially handle the above-described fields, but 14 unmarshal everything else normally. To handle fields with custom 15 unmarshalers, first we unmarshal them into a json.RawMessage, then call 16 the custom unmarshaler. Interface-typed fields are similar, except 17 instead of a custom unmarshaler we call the helper we've generated 18 (see unmarshal_helper.go.tmpl). Embedded fields don't need the 19 json.RawMessage; we just unmarshal our input again into the embedded 20 field. */}} 21 22 func (v *{{.GoName}}) UnmarshalJSON(b []byte) error { 23 {{/* Standard convention for unmarshalers is to no-op on null. */}} 24 if string(b) == "null" { 25 return nil 26 } 27 28 {{/* For our first pass, we ignore embedded fields, unmarshal all the 29 custom-unmarshaler or abstract fields into json.RawMessage, and 30 unmarshal everything else normally. To do this, we want to call 31 json.Unmarshal on the receiver (v). But if we do that naively on a 32 value of type <.GoName>, it will call this function again, and recurse 33 infinitely. So we make a wrapper type which embeds both this type and 34 NoUmnarshalJSON, which prevents either's UnmarshalJSON method from 35 being promoted. For more on why this is so difficult, see 36 https://github.com/benjaminjkraft/notes/blob/master/go-json-interfaces.md. 37 (Note there are a few different ways "hide" the method, but this one 38 seems to be the best option that works if this type has embedded types 39 with UnmarshalJSON methods.) 40 */}} 41 42 {{/* TODO(benkraft): Omit/simplify the first pass if all fields are 43 embedded/abstract. */ -}} 44 var firstPass struct{ 45 *{{.GoName}} 46 {{range .Fields -}} 47 {{if and .NeedsMarshaling (not .IsEmbedded) -}} 48 {{.GoName}} {{repeat .GoType.SliceDepth "[]"}}{{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"` 49 {{end -}} 50 {{end -}} 51 {{/* TODO(benkraft): In principle you might have a field-name that 52 conflicts with this one; avoid that. */ -}} 53 {{ref "github.com/Khan/genqlient/graphql.NoUnmarshalJSON"}} 54 } 55 firstPass.{{.GoName}} = v 56 57 err := {{ref "encoding/json.Unmarshal"}}(b, &firstPass) 58 if err != nil { 59 return err 60 } 61 62 {{/* Now, handle the fields needing special handling. */}} 63 {{range $field := .Fields -}} 64 {{if $field.NeedsMarshaling -}} 65 {{if $field.IsEmbedded -}} 66 {{/* Embedded fields are easier: we just unmarshal the same input into 67 them. (They're also easier because they can't be lists, since they 68 arise from GraphQL fragment spreads.) 69 70 Note that our behavior if you have two fields of the same name via 71 different embeds differs from ordinary json-unmarshaling: we unmarshal 72 into *all* of the fields. See goStructType.FlattenedFields in 73 types.go for more discussion of embedding and visibility. */ -}} 74 err = {{$field.Unmarshaler $.Generator}}( 75 b, &v.{{$field.GoType.Unwrap.Reference}}) 76 if err != nil { 77 return err 78 } 79 {{else -}} 80 {{/* For other fields (abstract or custom unmarshaler), first, call the 81 unmarshaler (our unmarshal-helper, or the user-specified one, 82 respectively). This gets a little complicated because we may have 83 a slice field. So what we do is basically, for each field of type 84 `[][]...[]MyType`: 85 86 dst := &v.MyField // *[][]...[]MyType 87 src := firstPass.MyField // [][]...[]json.RawMessage 88 89 // repeat the following three lines n times; each time, inside 90 // the loop we have one less layer of slice on src and dst 91 *dst = make([][]...[]MyType, len(src)) 92 for i, src := range src { 93 // We need the &(*dst)[i] because at each stage we want to 94 // keep dst as a pointer. (It only really has to be a 95 // pointer at the innermost level, but it's easiest to be 96 // consistent.) 97 dst := &(*dst)[i] 98 99 // (now we have `dst *MyType` and `src json.RawMessage`) 100 __unmarshalMyType(dst, src) 101 102 } // (also n times) 103 104 Note that if the field also uses a pointer (`[][]...[]*MyType`), we 105 now pass around `*[][]...[]*MyType`; again in principle 106 `[][]...[]*MyType` would work but require more special-casing. Thus 107 in the innermost loop, `dst` is of type `**MyType`, so we have to 108 pass `*dst` to the unmarshaler. Of course, since MyType is an 109 interface, I'm not sure why you'd any of that anyway. 110 111 One additional trick is we wrap everything above in a block ({ ... }), 112 so that the variables dst and src may take on different types for 113 each field we are handling, which would otherwise conflict. (We could 114 instead suffix the names, but that makes things much harder to read.) 115 */}} 116 { 117 dst := &v.{{$field.GoName}} 118 src := firstPass.{{$field.GoName}} 119 {{range $i := intRange $field.GoType.SliceDepth -}} 120 *dst = make( 121 {{repeat (sub $field.GoType.SliceDepth $i) "[]"}}{{if $field.GoType.IsPointer}}*{{end}}{{$field.GoType.Unwrap.Reference}}, 122 len(src)) 123 for i, src := range src { 124 dst := &(*dst)[i] 125 {{end -}} 126 {{/* dst now has type *<GoType>; dst is json.RawMessage */ -}} 127 {{/* If the field is null in the input, skip calling unmarshaler. 128 (This matches json.Unmarshal.) If the field is missing entirely 129 from the input, we will have an uninitialized json.RawMessage; 130 handle that likewise. */ -}} 131 if len(src) != 0 && string(src) != "null" { 132 {{if $field.GoType.IsPointer -}} 133 {{/* In this case, the parent for loop did `make([]*MyType, ...)` 134 and we have a pointer into that list. But we actually still 135 need to initialize the *elements* of the list. */ -}} 136 *dst = new({{$field.GoType.Unwrap.Reference}}) 137 {{end -}} 138 err = {{$field.Unmarshaler $.Generator}}( 139 src, {{if $field.GoType.IsPointer}}*{{end}}dst) 140 if err != nil { 141 return fmt.Errorf( 142 "unable to unmarshal {{$.GoName}}.{{$field.GoName}}: %w", err) 143 } 144 } 145 {{range $i := intRange $field.GoType.SliceDepth -}} 146 } 147 {{end -}} 148 } 149 {{end}}{{/* end if/else .IsEmbedded */ -}} 150 {{end}}{{/* end if .NeedsMarshaling */ -}} 151 {{end}}{{/* end range .Fields */ -}} 152 153 return nil 154 }