# Handling odd JSON decoding in Go

I was recently writing a client for a JSON-based API that had some unique behavior. The API lets you look up ham radio callsigns and their associated information. If the callsign exists you get back a JSON object with those details.

The JSON of a valid callsign is as follows (redacted for privacy):

```json
{  
   "hamdb":{  
      "version":"1",
      "callsign":{
         "call":"redacted",
         "class":"E",
         "expires":"09/25/2025",
         "status":"A",
         "grid":"redacted",
         "lat":"redacted",
         "lon":"redacted",
         "fname":"redacted",
         "mi":"redacted",
         "name":"redacted",
         "suffix":"redacted",
         "addr1":"redacted",
         "addr2":"redacted",
         "state":"redacted",
         "zip":"redacted",
         "country":"United States"
      },
      "messages":{
         "status":"OK"
      }
   }
}
```

But look what happens when you query for a callsign that can't be found:

```json
{  
   "hamdb":{  
      "version":"1",
      "callsign":{
         "call":"NOT_FOUND",
         "class":"NOT_FOUND",
         "expires":"NOT_FOUND",
         "status":"NOT_FOUND",
         "grid":"NOT_FOUND",
         "lat":"NOT_FOUND",
         "lon":"NOT_FOUND",
         "fname":"NOT_FOUND",
         "mi":"NOT_FOUND",
         "name":"NOT_FOUND",
         "suffix":"NOT_FOUND",
         "addr1":"NOT_FOUND",
         "addr2":"NOT_FOUND",
         "state":"NOT_FOUND",
         "zip":"NOT_FOUND",
         "country":"NOT_FOUND"
      },
      "messages":{
         "status":"NOT_FOUND"
      }
   }
}
```

Every field is `NOT_FOUND`. This makes unmarshalling that data into a struct a real challenge. The `expires` field can be either a parseable date or a string!

The solution I came up with was to introduce a new generic type that can handle this behavior:

```go
type Unknowable[K comparable] struct {
    Value K
    Known bool
}

func (u *Unknowable[K]) UnmarshalJSON(b []byte) error {
    s := strings.Replace(string(b), `"`, ``, -1)
    switch s {
    case "NOT_FOUND":
        u.Known = false
        return nil
    default:
        u.Known = true
        var v K

        switch any(v).(type) {
        case float64:
            f, err := strconv.ParseFloat(s, 64)
            if err != nil {
                return err
            }
            v = any(f).(K)

        case int:
            f, err := strconv.Atoi(s)
            if err != nil {
                return err
            }
            v = any(f).(K)

        default:
            if err := json.Unmarshal(b, &v); err != nil {
                return err
            }
        }

        u.Value = v
    }
    return nil
}
```

The goofy `any(v).(type)` switch is because I couldn't quite figure out how to get the concrete types to parse correctly without doing something different to each of them. This is partly because I wanted to have real types for things like the `lat` and `lon`, not just strings.

Now I can represent the types I want to unmarshal within the destination struct directly.

```go
type Callsign struct {
	Call          string                `json:"call"`
	Class         LicenseClass          `json:"class"`
	Expires       Unknowable[time.Time] `json:"expires"`
	Status        string                `json:"status"`
	Grid          string                `json:"grid"`
	Lat           Unknowable[float64]   `json:"lat"`
	Lon           Unknowable[float64]   `json:"lon"`
	FirstName     string                `json:"fname"`
	MiddleInitial string                `json:"mi"`
	LastName      string                `json:"name"`
	Suffix        string                `json:"suffix"`
	Address       string                `json:"addr1"`
	City          string                `json:"addr2"`
	State         string                `json:"state"`
	Zip           Unknowable[int]       `json:"zip"`
	Country       string                `json:"country"`
}
```

This is one of those times where generics in Go has been super helpful. Granted, I'd still like a way to type-switch on a constraint, but this is still much cleaner than having multiple types with nearly identical implementations and names.
