1

I am new in Golang and need some help! I have several questions.

In PostgreSQL database I have table called surveys.

CREATE TABLE SURVEYS(
  SURVEY_ID UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
  SURVEY_NAME VARCHAR NOT NULL,
  SURVEY_DESCRIPTION TEXT,
  START_PERIOD TIMESTAMP,
  END_PERIOD TIMESTAMP
);

As you can see SURVEY_ID column is PRIMARY KEY and it's type is UUID4.

In Golang application I create such struct to this table:

type Survey struct {
    ID string `json:"survey_id"`
    Name string `json:"survey_name"`
    Description utils.NullString `json:"survey_description"`
    StartPeriod utils.NullTime `json:"start_period"`
    EndPeriod utils.NullTime `json:"end_period"`
}

As you can see type of ID field is string. Is it correct? I am not sure that it's best practice.

My second question about strange result which I have when make GET request to specific survey by it's ID.

For example when I make such request:

http://localhost:8000/api/survey/0cf1cf18-d5fd-474e-a8be-754fbdc89720

As response I have this:

{
    "survey_id": "0cf1cf18-d5fd-474e-a8be-754fbdc89720",
    "survey_name": "NAME",
    "survey_description": {
        "String": "DESCRIPTION",
        "Valid": true
    },
    "start_period": {
        "Time": "2019-01-01T00:00:00Z",
        "Valid": false
    },
    "end_period": {
        "Time": "0001-01-01T00:00:00Z",
        "Valid": false
    }
}

As you can see something wrong with last 3 field: survey_description, start_period and end_period. I want to see key and value in one line. For example as here:

{
    "survey_id": "0cf1cf18-d5fd-474e-a8be-754fbdc89720",
    "survey_name": "NAME",
    "survey_description": "DESCRIPTION",
    "start_period": "2019-01-01 00:00:00",
    "end_period": null
}

Where exactly I make mistake in my code?

utils.go:

package utils

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "github.com/lib/pq"
    "time"
)

// NullTime is an alias for pq.NullTime data type.
type NullTime struct {
    pq.NullTime
}

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

// UnmarshalJSON for NullTime.
func (nt *NullTime) UnmarshalJSON(b []byte) error {
    err := json.Unmarshal(b, &nt.Time)
    nt.Valid = err == nil
    return err
}

// NullInt64 is an alias for sql.NullInt64 data type.
type NullInt64 struct {
    sql.NullInt64
}

// MarshalJSON for NullInt64.
func (ni *NullInt64) MarshalJSON() ([]byte, error) {
    if !ni.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ni.Int64)
}

// UnmarshalJSON for NullInt64.
func (ni *NullInt64) UnmarshalJSON(b []byte) error {
    err := json.Unmarshal(b, &ni.Int64)
    ni.Valid = err == nil
    return err
}

// NullString is an alias for sql.NullString data type.
type NullString struct {
    sql.NullString
}

// MarshalJSON for NullString.
func (ns *NullString) MarshalJSON() ([]byte, error) {
    if !ns.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ns.String)
}

// UnmarshalJSON for NullString.
func (ns *NullString) UnmarshalJSON(b []byte) error {
    err := json.Unmarshal(b, &ns.String)
    ns.Valid = err == nil
    return err
}

routes.go:

router.HandleFunc("/api/survey/{survey_id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", controllers.GetSurvey).Methods("GET")

controllers/survey.go:

var GetSurvey = func(responseWriter http.ResponseWriter, request *http.Request) {
    // Initialize variables.
    survey := models.Survey{}
    var err error

    vars := mux.Vars(request)

    // Execute SQL statement.
    err = database.DB.QueryRow("SELECT * FROM surveys WHERE survey_id = $1;", vars["survey_id"]).Scan(&survey.ID, &survey.Name, &survey.Description, &survey.StartPeriod, &survey.EndPeriod)

    // Shape the response depending on the result of the previous command.
    if err != nil {
        log.Println(err)
        switch err {
        case sql.ErrNoRows:
            utils.ResponseWithError(responseWriter, http.StatusNotFound, "The entry not found.")
        default:
            utils.ResponseWithError(responseWriter, http.StatusInternalServerError, err.Error())
        }
        return
    }
    utils.Response(responseWriter, http.StatusOK, survey)
}

2 Answers 2

1

Well, finally I found the result.

I changed the struct for the table:

type Survey struct {
    ID string `json:"survey_id"`
    Name string `json:"survey_name"`
    Description *string `json:"survey_description", sql:"index"`
    StartPeriod *time.Time `json:"start_period", sql:"index"`
    EndPeriod *time.Time `json:"end_period", sql:"index"`
}
Sign up to request clarification or add additional context in comments.

Comments

0

I don't see any issue with using a string for a UUID.

As for the MarshalJSON not working, I think I know what's going on. Your null types don't implement MarshalJSON, only the pointers to them. The fix would be to either change the function to use a non-pointer receiver, or make the fields pointers in your struct.

func (ns *NullString) MarshalJSON() ([]byte, error)

If you did make them pointers, then you could just keep them like that, since they're nullable.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.