Home > database >  How to validate allowed fields in json body in go validation
How to validate allowed fields in json body in go validation

Time:11-08

I have a go struct which I'm using for my POST of an entity

type Student struct {
   ID          string     `json:"id" firestore:"id"`
   Name        string     `json:"name" validate:"required" firestore:"name"`
}

From the POST body request I can send body as

{
"id" : 123,
"name" : "Student Name"
}

I want to implement a functionality where the request should fail while doing the validation saying "id" field in POST body is not allowed.

As I'm planning to reuse the same struct for GET I'm unable to skip the "id" in json marshalling and unmarshalling.

Is there any struct tag like allowed:true or false ?

I tried to skip the json validation but I want to reuse the same struct i was unable to proceed.

In the code logic i can just every time override it to empty value but it doesn't seem to be good way to add custom logic for updating the fields after converting into object.

Saw various validate struct tag but didn't find any that will match the use case

CodePudding user response:

From what I understood, one of the various solutions that can work for you is by using the len valitor. Thanks to it, you can enforce the provided struct to not have the ID value set. I saw that in your example the ID is treated as a string which means that the zero-value for it is "".
Below, you can find a complete working example:

package main

import (
    "encoding/json"
    "fmt"

    "github.com/go-playground/validator/v10"
)

type Student struct {
    ID   string `json:"id" validate:"len=0"`
    Name string `json:"name" validate:"required"`
}

func main() {
    validator := validator.New()
    var student Student

    studentOk := `{"id": "", "name": "John Doe"}`
    err := json.Unmarshal([]byte(studentOk), &student)
    if err != nil {
        panic(err)
    }

    err = validator.Struct(student)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("fine")
    }

    studentIdNotProvided := `{"name": "John Doe"}`
    err = json.Unmarshal([]byte(studentIdNotProvided), &student)
    if err != nil {
        panic(err)
    }
    err = validator.Struct(student)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("fine")
    }

    studentKo := `{"id": "123", "name": "John Doe"}`
    err = json.Unmarshal([]byte(studentKo), &student)
    if err != nil {
        panic(err)
    }
    err = validator.Struct(student)
    if err != nil {
        fmt.Println(err)
    }
}

Every possible scenario that you might face should be covered:

  1. JSON request with the ID set to ""
  2. JSON request with ID not provided at all
  3. Faulty JSON request that specifies an ID

Be careful, if you treat the ID as an integer, you will have to switch the validation to something like != 0 as the zero-value for an integer is 0.

Let me know if this solves your question.

CodePudding user response:

This is my updated solution. I give you two different approaches:

  1. Create a tiny struct with only the relevant fields that you want from the input request (preferred approach)
  2. Unmarshal onto a map and assign only the fields that are relevant to you

Below, you can find the complete working code:

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Example struct {
    ID          string     `json:"id"`
    Name        string     `json:"name" validate:"required"`
    Count       int        `json:"count"`
    CreatedTime *time.Time `json:"created_time"`
    UpdatedTime *time.Time `json:"updated_time,omitempty"`
    DeletedTime *time.Time `json:"deleted_time,omitempty"`
    ExpiresAt   *time.Time `json:"expires_at,omitempty"`
}

type ExamplePost struct {
    Name string `json:"name" validate:"required"`
}

func main() {
    jsonReq := `{"ID":"123","Name":"John Doe","Count":1,"created_time":"2022-11-07 16:52:41.196032353  0100","updated_time":"2022-11-07 16:52:41.196032353  0100","deleted_time":"2022-11-07 16:52:41.196032353  0100","expires_at":"2022-11-07 16:52:41.196032353  0100"}`

    fmt.Println("first approach: with a tiny struct")
    var examplePost ExamplePost
    err := json.Unmarshal([]byte(jsonReq), &examplePost)
    if err != nil {
        panic(err)
    }

    fmt.Printf("name: %q\n", examplePost.Name)

    postMap := make(map[string]interface{}, 0)
    err = json.Unmarshal([]byte(jsonReq), &postMap)
    if err != nil {
        panic(err)
    }

    fmt.Println("second approach: with a map")
    var example Example
    for k, v := range postMap {
        if k == "Name" {
            if name, ok := v.(string); ok {
                example.Name = name
            } else {
                example.Name = ""
            }
        }
    }

    fmt.Printf("name: %q\n", example.Name)
}

The second approach requires more effort as you've to unmarshal the incoming JSON request, loop over its keys and values, select the relevant ones, type casting values to be sure that are in the right format, and, finally, assign to your struct.
Hope this solve your issue.

  • Related