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:
- JSON request with the ID set to
""
- JSON request with ID not provided at all
- 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 is0
.
Let me know if this solves your question.
CodePudding user response:
This is my updated solution. I give you two different approaches:
- Create a tiny struct with only the relevant fields that you want from the input request (preferred approach)
- 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.