1

I was reading this following article: https://www.ribice.ba/golang-enums/

There is a function defined in one of the code samples:

func (lt *LeaveType) UnmarshalJSON(b []byte) error {
    // Define a secondary type to avoid ending up with a recursive call to json.Unmarshal
    type LT LeaveType;
    var r *LT = (*LT)(lt);
    err := json.Unmarshal(b, &r)
    if err != nil{
        panic(err)
    }
    switch *lt {
    case AnnualLeave, Sick, BankHoliday, Other:
        return nil
    }
    return errors.New("Inalid leave type")
}

What is the syntax var r *LT = (*LT)(lt); doing in this example?

icza
  • 389,944
  • 63
  • 907
  • 827
Christian Gossain
  • 5,942
  • 12
  • 53
  • 85

5 Answers5

7

Go technically does not have casts but rather conversions. The syntax for an explicit conversion is T(x) where T is some type and x is some value that is convertible to that type. See Conversions in the Go specification for details.

As you can see from the function's declaration:

func (lt *LeaveType) UnmarshalJSON(b []byte) error {

lt itself has type pointer to LeaveType and UnmarshalJSON is a receiver function for type *LeaveType. The encoding/json package will call such a function to decode input JSON when the variable that the package would like to set has type LeaveType (or *LeaveType—the package will create the LeaveType variable itself in this case).

As the comment in the code says, the author of the code would now like to have the encoding/json code unmarshal the JSON as if there weren't a function UnmarshalJSON. But there is a function UnmarshalJSON, so if we just invoke the encoding/json code without a little bit of trickery, encoding/json will just call this function again, leading to infinite recursion.

By defining a new type LT whose contents are exactly the same as the existing type LeaveType, we end up with a new type that does not have a receiver function. Invoking the encoding/json on an instance of this type (or of a pointer to this type) won't call the *LeaveType receiver, because LT is a different type, even though its contents match up exactly.

We could do this:

func (lt *LeaveType) UnmarshalJSON(b []byte) error {
    type LT LeaveType
    var r LT
    err := json.Unmarshal(b, &r)
    if err != nil {
        panic(err)
    }
    // ...
}

This would fill in r, which has the same size and shape as any LeaveType variable. Then we could use the filled-in r to set *lt:

*lt = LeaveType(r) // an ordinary conversion

after which we could keep going as before, using *lt as the value. But this means that UnmarshalJSON had to set a temporary variable r, which we then had to copy to its final destination. Why not, instead, set up something so that UnmarshalJSON fills in the target variable, but using the type we chose?

That's what the syntax here is for. It's not the shortest version: as Cerise Limón noted, there is a shorter way to spell it (and that shorter spelling is generally preferred). The first set of parentheses in (*LT)(lt) is required to bind the *—the pointer to part—to the LT, as *LT(lt) has the wrong binding: it means the same thing as *(LT(lt)) which is not what we want.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Excellent! Thank you for the detailed breakdown. I'm just learning Go and trying to wrap my head around the syntax. I was getting confused between type assertions (with the dot syntax) and type conversions. Also the parentheses around the pointer part makes sense! – Christian Gossain Feb 27 '20 at 20:11
6

The expression (*LT)(lt) is a conversion to type *LT.

The statement var r *LT = (*LT)(lt); declares variable r as type *LT with initial value (*LT)(lt). The statement can be written more simply as r := (*LT)(lt). There's no need to mention the type twice or to end the line with a semicolon.

The function declares type LT with empty method set to avoid a recursive call to UnMarshalJSON.

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
4

json.Unmarshal() unmarshals some JSON text into a Go value. If the value to unmarshal into implements the json.Unmarshaler interface, its UnmarshalJSON() method is called which allows to implement custom unmarshaling logic.

Quoting from json.Unmarshal():

To unmarshal JSON into a value implementing the Unmarshaler interface, Unmarshal calls that value's UnmarshalJSON method, including when the input is a JSON null.

The json.Unmarshaler interface:

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

LeaveType (or more specifically *LeaveType) has an UnmarshalJSON() method which we can see in the question, so it implements json.Unmarshaler.

And the LeaveType.UnmarshalJSON() method wishes to use the default unmarshaling logic which does the "hard" part, and just wants to make some final adjustments. So it calls json.Unmarshal():

err := json.Unmarshal(b, &r)

If we would pass lt to unmarshal into, –since lt implements json.UnmashalerLeaveType.UnmarshalJSON() would be called by the json package, effectively causing an infinite "recursion".

Of course, this is not what we want. In order to avoid the infinite recursion, we have to pass a value that does not implement json.Unmarshaler, a value whose type does not have an UnmarshalJSON() method.

This is where creating a new type comes into the picture:

type LT LeaveType

The type keyword creates a new type called LT which is distinct from LeaveType. It does not "inherit" any of LeaveType's methods, so LT does not implement json.Unmarshaler. So if we pass a value of LT or *LT to json.Unmarshal(), it will not result in LeaveType.UnmarshalJSON() to be called (by the json package).

var r *LT = (*LT)(lt)

This declares a variable named r, whose type is *LT. And it assigns the value lt converted to *LT. The conversion is needed because lt is of type *LeaveType, so it cannot be assigned to a variable of type *LT, but since LT has LeaveType as its underlying type, *LeaveType is convertible to *LT.

So r is a pointer, it points to the same value as lt, it has the same memory layout. So if we use the default unmarshaling logic and "populate" the struct pointed by r, then the "same" struct pointed by lt will be populated.

See related / similar question: Call json.Unmarshal inside UnmarshalJSON function without causing stack overflow

icza
  • 389,944
  • 63
  • 907
  • 827
3

It's casting lt, a LeaveType pointer, to an LT pointer.

LT is defined just above by type LT LeaveType; to be equivalent to LeaveType.

It's doing this for the reasons explained in the comment.

// Define a secondary type to avoid ending up with a recursive call to json.Unmarshal

Whether this is effective or necessary, I don't know.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • The nested type is effective/necessary. See an equivalent issue with the Stringer interface [here](https://play.golang.org/p/IEw_uhdinBJ). – colm.anseo Feb 27 '20 at 20:45
1

You can see the same effect in play with a simply Stringer interface example, where the fmt.Println function will try to marshal data into string format. If a given value's type has a String() method, it will be used in preference to reflection.

This implementation fails (and go vet issues a warning) as it causes infinite recursion:

type mystring string
func (ms mystring) String() string {
    return fmt.Sprintf("mystring: %s", ms)
}

This version is essential what the original code is doing:

type mystring2 string

func (ms mystring2) String() string {
    type mystring2 string // <- local type mystring2 overrides global type
    v := mystring2(ms)

    return fmt.Sprintf("mystring2: %s", v)
}

Remove the type mystring2 string line and see what happens.

colm.anseo
  • 19,337
  • 4
  • 43
  • 52