I have a situation where generics would be useful: a server (that I do not control or influence) with many API endpoints that each returns very similar json. There’s an envelope with common attributes and then an embedded named substructure (the name differs in the return value of each call) of a different type.

Without generics, you could do something like:

type Base struct {
   // common fields
}

type A {
   Base
   A struct {
      // subtype fields
   }
}

type B {
   Base
   B struct {
      // subtype fields
   }
}

but then you’d need to either duplicate a bunch of API calling and unmarshalling code, or consolidate it and do a bunch of type casting and checking.

Generics to the rescue: subtypes become specific types for a general type:

type Base[T any] {
   // common fields
   Subfield T
}

type A struct {
  // subtype fields
}

type B struct {
  // subtype fields
}

It even looks cleaner! Ah, but the rub is that the marshaled field name Subfield is the same for every type: there’s no way to specify a tag for a struct type so that Subfield is un/marshaled with a name specific to the type.

https://go.dev/play/p/3ciyUITYZHk

The only thing I can think of is to create a custom unmarshaller for Base and use introspection to handle the specific type.

Am I missing a less hacky (introspection is always hacky) way to set a default tag for any field of a given struct type? How would you do this?

This pattern - APIs using envelopes for data packets - is exceedingly common. I can’t believe the only way to solve it on Go is by either mass code duplication, or introspection.

  • DeprecatedCompatV2@programming.dev
    link
    fedilink
    arrow-up
    3
    ·
    edit-2
    9 days ago

    I don’t know the Go answer, but this problem exists in other languages and platforms. It can be incredibly annoying when the http client library doesn’t support unwrapping, even with generics.

    If you need the subfield to have a name based on the type, you’re going to need introspection / code generation.