A common function to read and write JSON data for two different structs and from two different JSON files

48 Views Asked by At

I'm implementing a system where I have two structs one for a User and another one for the Worker

>! The user section
type User struct {
    Email    string `json:"email"`
    Name     string `json:"name"`
    Password string `json:"password"`
}

type Users struct {
    Users []User `json:"users"`
}
>! The worker section
type Worker struct {
    Email    string `json:"email"`
    Name     string `json:"name"`
    Password string `json:"password"`
}

type Workers struct {
    Workers []Worker `json:"workers"`
}

Both the structs are stored in two different files according to their function: user.go and worker.go. Here's a sample code for the user.go file basically calling the readUser() function from the jsonhandler.go file.

// Check if the user already exist or not
func check(email string) (bool, User) {
    users := readUser()

    for i := 0; i < len(users.Users); i++ {
        if email == users.Users[i].Email {
            return true, users.Users[i]
        }
    }

    return false, User{}
}

func UserSignIn(c *fiber.Ctx) error {
    // Function to add a new user to the database and check if it already exists
    // send en error if there is a problem while parsing the JSON data
    user := new(User)
    if err := c.BodyParser(&user); err != nil {
        return errorResponse(c, err)
    }

    // Check if the user is available and send the response accordingly
    if isAvilable, _ := check(user.Email); isAvilable {
        response200(c, "user already exist")
    } else {
        return response200(c, "user doesn't exist")
    }

    return nil
}

I have created the same functions for handling the worker struct (for example checkWorker() and WorkerSignIn()) basically doing the same thing. But, I also had to create another function readWorker() to read values for the worker struct. In the jsonhandler.go file I have created a function to read data from the JSON file.

func readUser() Users {
    var users Users
    file, _ := os.Open("user.json")
    defer file.Close()

    byteValue, _ := io.ReadAll(file)
    json.Unmarshal(byteValue, &users)

    return users
}

But it cannot read data from the worker.json file from the same function so I had to create another function readWorker() basically doing the same thing.

I tried to create a common function that returns an interface{} type but I cannot access the values, for example I was getting errors in operations like:

for i := 0; i < len(users.Users); i++ {
    if email == users.Users[i].Email {
        return true, users.Users[i]
    }
}

I'm getting error while accessing the Email and users.Users fields.

3

There are 3 best solutions below

0
Adem_Bc On

You can use type assertion to convert interface{} to your struct type. this is an example

    user := readUsers().(*types.Users)
2
coxley On

You can do it with an interface and type assert, or use generics and keep the types.

As a side note, you really want to return errors and not ignore them. You'll end up with really hard to debug programs otherwise.


func readJSON[T any](file string) (T, error) {
    f, err := os.Open(file)
    if err != nil {
        var zero T
        return zero, err
    }
    defer f.Close()

    var val T
    dec := json.NewDecoder(f)
    if err := dec.Decode(&val); err != nil {
        var zero T
        return zero, err
    }
    return val, nil
}

// Use as...
const usersFile = "user.json"
const workersFile = "worker.json"

users, err := readJSON[Users](usersFile)
workers, err := readJSON[Workers](workersFile)

// Or if you'd prefer to not manually give the type parameters,
// you can pass type in as an argument for better inference.

// The [P *T] ensures that 'val' is always a pointer
func readJSON[T any, P *T](file string, val P) error {
    f, err := os.Open(file)
    if err != nil {
        return err
    }
    defer f.Close()

    return json.NewDecoder(f).Decode(val)
}

var users Users
if err := readJSON(usersFile, &users); err != nil {
    // log / act on it
}

var workers Workers
if err := readJSON(workersFile, &workers); err != nil {
    // log / act on it
}
0
PRATHEESH PC On

You can create a function which accepts the json file path and a binder object

func ReadJson(filePath string, bind interface{}) error {
    file, _ := os.Open(filePath)
    defer file.Close()

    byteValue, _ := io.ReadAll(file)
    err := json.Unmarshal(byteValue, bind)
    return err
}

Here the bind parameter represents a pointer to the object where you aim to unmarshal the JSON data. And you can use as

var users Users
err := ReadJson("user.json", &users)
if err != nil {
    panic(err)
}

var workers Workers
err = ReadJson("worker.json", &workers)
if err != nil {
    panic(err)
}