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  }