Search code examples
sqlgonull-string

Unmarshalling a sql.NullTime struct in Go


Given

type NullTime struct {
    Time  time.Time
    Valid bool // Valid is true if Time is not NULL
}

and

type PayinCount struct {
    DateShiftStart sql.NullTime    `json:"dateShiftStart"`
    DateShiftEnd   sql.NullTime    `json:"dateShiftend"`
}

when I process the following JSON

{
    "dateShiftStart":"2023-10-16",
    "dateShiftEnd":"2023-10-23"
}

with

var payinsCount PayinsCount
err = json.Unmarshal(body, &payinsCount)
if err != nil {
    sendErrorResponse(w, err.Error(), http.StatusBadRequest)
    return
}

where sendErrorResponse is the following helper process

func sendErrorResponse(w http.ResponseWriter, err string, statusCode int) {
    messageStatusCode := MessageStatusCode{
        Message:    err,
        StatusCode: statusCode}
    w.WriteHeader(statusCode)
    json.NewEncoder(w).Encode(messageStatusCode)
}

I get the following message

{
    "message": "json: cannot unmarshal string into Go struct field PayinsCount.dateShiftStart of type sql.NullTime",
    "statusCode": 400
}

How do I resolve this issue?


Solution

  • I ended up using the following. I added the following type.

    type NullDate sql.NullTime
    

    then I changed PayinsCount to use NullDate

    type PayinsCount struct {
        DateShiftStart NullDate      `json:"dateShiftStart,omitempty"`
        DateShiftEnd   NullDate      `json:"dateShiftend,omitempty"`
    }
    

    then I created

    // UnmarshalJSON for NullDate
    func (nd *NullDate) UnmarshalJSON(b []byte) error {
        s := string(b)
        s = strings.ReplaceAll(s, "\"", "")
    
        x, err := time.Parse(time.DateOnly, s)
        if err != nil {
            nd.Valid = false
            return err
        }
    
        nd.Time = x
        nd.Valid = true
        return nil
    }
    

    now when I process the following JSON

    {
        "dateShiftStart":"2023-10-16",
        "dateShiftEnd":"2023-10-23"
    }
    

    with

    var payinsCount PayinsCount
    err = json.Unmarshal(body, &payinsCount)
    if err != nil {
        sendErrorResponse(w, err.Error(), http.StatusBadRequest)
        return
    }
    

    it works. I end up with a valid PayinsCount instance.

    For completeness here is the MarshalJSON function for NullDate

    // MarshalJSON for NullDate
    func (nd NullDate) MarshalJSON() ([]byte, error) {
        if !nd.Valid {
            return []byte("null"), nil
        }
        val := fmt.Sprintf("\"%s\"", nd.Time.Format(time.DateOnly))
        return []byte(val), nil
    }
    

    Note the escaped double quotes - without them the encoding/json marshalling code was processing the date string in 3 chunks and I was getting the following error

    error(*encoding/json.SyntaxError) *{msg: "invalid character '-' after top-level value", Offset: 0}