Godantic is a Go package for inspecting and validating JSON-like data against Go struct types and schemas. It provides functionalities for checking type compatibility, structure compatibility, and other validations such as empty string, invalid time, minimum length list checks, regex pattern matching, and format validation.
Install the godantic package:
go get github.com/grahms/godantic
Then import it in your Go code:
import "github.com/grahms/godantic"
type Person struct {
Name *string `json:"name" binding:"required"`
Age *int `json:"age"`
}
var jsonData = []byte(`{"name": "John", "age": 30}`)
var person Person
validator := godantic.Validate{}
err := validator.BindJSON(jsonData, &person)
if err != nil {
fmt.Println(err)
}
- Enum Validation
type Person struct {
Name *string `json:"name" binding:"required"`
Role *string `json:"role" enum:"admin,user"`
}
// Here, the Role field must be either 'admin' or 'user'. If it's not, an error is returned.
- Handling Extra Fields
var jsonData = []byte(`{"name": "John", "age": 30, "extra": "extra data"}`)
var person Person
validator := godantic.Validate{}
err := validator.BindJSON(jsonData, &person)
if err != nil {
fmt.Println(err) // This will print an error about the 'extra' field not being valid.
}
- Custom Error Handling
type CustomError struct {
ErrType string
Message string
Path string
err error
}
func (e *CustomError) Error() string {
e.err = errors.New(e.Message)
return e.err.Error()
}
// Now you can create your own error type and return it in your custom validation functions.
- Inspecting and Validating Structs
validator := godantic.Validate{}
err := validator.InspectStruct(&myStruct)
if err != nil {
fmt.Println(err)
}
type Address struct {
City *string `json:"city" binding:"required"`
State *string `json:"state" binding:"required"`
}
type Person struct {
Name *string `json:"name" binding:"required"`
Age *int `json:"age"`
Address *Address `json:"address"`
}
var jsonData = []byte(`{
"name": "John",
"age": 30,
"address": {
"city": "New York",
"state": "NY"
}
}`)
var person Person
validator := godantic.Validate{}
err := validator.BindJSON(jsonData, &person)
if err != nil {
fmt.Println(err)
}
In this example, the Person
struct has a nested Address
struct. The godantic
package will validate the fields of the nested struct as well.
type Skill struct {
Name *string `json:"name" binding:"required"`
Level *int `json:"level"`
}
type Person struct {
Name *string `json:"name" binding:"required"`
Age *int `json:"age"`
Skills []Skill `json:"skills"`
}
var jsonData = []byte(`{
"name": "John",
"age": 30,
"skills": [
{
"name": "Go",
"level": 5
},
{
"name": "Python",
"level": 4
}
]
}`)
var person Person
validator := godantic.Validate{}
err := validator.BindJSON(jsonData, &person)
if err != nil {
fmt.Println(err)
}
In this example, the Person
struct has a Skills
field that is a slice of Skill
structs. The godantic
package will iterate over the list and validate each object in the list.
Here's an example of how to use the godantic
package with the Gin web framework.
package main
import (
"github.com/gin-gonic/gin"
"github.com/grahms/godantic"
"net/http"
)
type User struct {
Name *string `json:"name" binding:"required"`
Email *string `json:"email" binding:"required"`
Age *int `json:"age"`
}
func main() {
r := gin.Default()
r.POST("/user", func(c *gin.Context) {
var user User
validator := godantic.Validate{}
jsonData, err := c.GetRawData()
if err != nil {
c.JSON(http
.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err = validator.BindJSON(jsonData, &user)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
r.Run()
}
In the example above, instead of using Gin's built-in JSON binding (c.BindJSON(&user)
), we're using godantic
's BindJSON
function. Here are the advantages:
-
More control over validation:
godantic
provides much more control over the validation process compared to Gin's built-in binding. It supports various validation methods and customizations like type compatibility checks, structure compatibility checks, and handling extra fields. You can customize these validation rules based on your needs. -
Detailed error reporting:
godantic
provides detailed error types and messages which can be very useful for debugging and for providing precise error messages to the API users. In contrast, Gin's built-in binding returns a generic "binding error". -
Enum Validation:
godantic
supports enum validation, which is not available in Gin's built-in JSON binding. -
Nested Fields & Objects:
godantic
supports validation for nested fields and objects as well as lists, which provides more flexibility and control compared to Gin's built-in binding.
Please remember that the Go's json.Unmarshal
function used by godantic
doesn't check for additional fields in the JSON input that are not present in the target struct. If you want to disallow additional fields, you might have to implement additional checks.
- BindJSON: Parses and validates JSON data into a provided struct. It performs type checking and structural validation against the expected schema of the provided struct.
- InspectStruct: Iteratively inspects the fields of a struct based on their type and validates them based on certain conditions.
- CheckTypeCompatibility: Checks if two
map[string]interface{}
objects (request and reference data) are compatible in terms of structure and type.
REQUIRED_FIELD_ERR
: Triggered when a field marked as required is not provided.INVALID_ENUM_ERR
: Triggered when a field value is not among the allowed enum values.INVALID_FIELD_ERR
: Triggered when an invalid field is provided.TYPE_MISMATCH_ERR
: Triggered when a field is given a value with an invalid type.SYNTAX_ERR
: Triggered when there is a syntax error in the JSON data.INVALID_JSON_ERR
: Triggered when the provided data is not valid JSON.EMPTY_JSON_ERR
: Triggered when the provided JSON data is empty.INVALID_TIME_ERR
: Triggered when a time.Time field has an invalid time value.EMPTY_STRING_ERR
: Triggered when a string field is empty.EMPTY_LIST_ERR
: Triggered when a list field is empty.INVALID_REGEX_ERR
: Triggered when a field value does not match the required regex pattern.INVALID_FORMAT_ERR
: Triggered when a field value does not match the required format.
The following table lists the supported format tags and their corresponding regular expressions:
Format Tag | Description | Example Use Case |
---|---|---|
Email address format | Validating user email addresses | |
url | URL format | Validating website URLs |
date | Date format (YYYY-MM-DD) | Validating dates in a specific format |
time | Time format (HH:MM:SS) | Validating times in a specific format |
uuid | UUID format | Validating UUIDs |
ip | IP address format | Validating IPv4 or IPv6 addresses |
credit_card | Credit card number format | Validating credit card numbers |
postal_code | Postal code format | Validating postal codes |
phone | Phone number format | Validating phone numbers |
ssn | Social Security Number format | Validating SSN |
credit_card_expiry | Credit card expiry date format | Validating credit card expiry dates |
latitude | Latitude format | Validating latitude coordinates |
longitude | Longitude format | Validating longitude coordinates |
hex_color | Hex color format | Validating hex color codes |
mac_address | MAC address format | Validating MAC addresses |
html_tag | HTML tag format | Validating HTML tags |
mz-msisdn | Mozambican phone number format | Validating Mozambican phone numbers |
mz-nuit | Mozambican NUIT format | Validating Mozambican NUIT numbers |
The ignore
tag allows you to exclude specific fields from input validation while still retaining them in the struct. This can be useful for fields representing metadata or internal information that shouldn't be validated during input but are required for other purposes.
Consider a User
struct with an ID
field that should be excluded from input validation but retained in the struct for internal use:
package main
import (
"fmt"
"github.com/grahms/godantic"
)
type User struct {
ID int `json:"id" binding:"ignore"` // ID field is ignored during input validation
FirstName string `json:"first_name" binding:"required"`
LastName string `json:"last_name" binding:"required"`
Email string `json:"email" binding:"required" format:"email"`
// Other fields...
}
func main() {
// Example JSON data representing user input
jsonData := []byte(`{
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com"
// No "id" field included
}`)
// Create a new instance of the validator
validator := godantic.Validate{}
// Create an instance of the User struct
var user User
// Bind and validate the JSON data against the User struct
err := validator.BindJSON(jsonData, &user)
if err != nil {
fmt.Println(err)
return
}
// Validation successful, process the user data
fmt.Printf("User ID: %d\n", user.ID) // ID is still accessible despite being ignored during validation
fmt.Printf("Name: %s %s\n", user.FirstName, user.LastName)
fmt.Printf("Email: %s\n", user.Email)
}
In this example:
- The
ID
field represents a unique identifier for the user and is marked with theignore
tag. - Despite being ignored during validation, the
ID
field remains accessible in theUser
struct after validation, allowing you to utilize it for internal operations or data processing.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License.
This README.md includes detailed information about how to use Godantic, including simple and advanced usage examples, integration with web frameworks, features, error types, supported format tags, and more. If you have any further updates or modifications, please let me know!