jsonschematics
is a Go package designed to validate and manipulate JSON data structures using schematics.
- Validate JSON objects against defined schematics
- Convert schematics to JSON schemas
- Handle complex data validation scenarios
- Perform operations on the data
To install the package, use the following command:
go get github.com/ashbeelghouri/jsonschematics@latest
You can validate JSON data against a defined schematic using the Validate
function. Here's an example:
package main
import (
"fmt"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
schema := jsonschematics.Schematics{
// Define your schema here
}
data := map[string]interface{}{
"Name": "John",
"Age": 30,
}
err := schema.Validate(data)
if err != nil {
fmt.Println("Validation errors:", err)
} else {
fmt.Println("Validation successful")
}
}
Instead of defining the schema directly, load the schema from a JSON file:
package main
import (
"fmt"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
var schematics jsonschematics.Schematics
err := jsonschematics.LoadSchemaFromFile("path-to-your-schema.json")
if err != nil {
fmt.Println("Unable to load the schema:", err)
} else {
fmt.Println("Schema Loaded Successfully")
}
}
If you want to load the schema from a map[string]interface{}
, you can use the example below:
package main
import (
"fmt"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
var schematics jsonschematics.Schematics
schema := map[string]interface{}{
// Define your schema here
}
err := jsonschematics.LoadSchemaFromMap(&schema)
if err != nil {
fmt.Println("Unable to load the schema:", err)
} else {
fmt.Println("Schema Loaded Successfully")
}
}
You can also add your own functions to validate the data:
package main
import (
"fmt"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
var schematics jsonschematics.Schematics
err := schematics.LoadSchemaFromFile("path-to-your-schema.json")
if err != nil {
fmt.Println("Unable to load the schema:", err)
}
schematics.Validators.RegisterValidator("StringIsInsideArr", StringInArr)
}
func StringInArr(i interface{}, attr map[string]interface{}) error {
str := i.(string)
strArr := attr["arr"].([]string)
found := false
if len(strArr) > 0 {
for _, item := range strArr {
if item == str {
found = true
}
}
}
if !found {
return fmt.Errorf("string not found in array")
}
return nil
}
package main
import (
"fmt"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
var schematics jsonschematics.Schematics
err := schematics.LoadSchemaFromFile("path-to-your-schema.json")
if err != nil {
fmt.Println("Unable to load the schema:", err)
}
schematics.Validators.RegisterValidator("StringIsInsideArr", func(i interface{}, attr map[string]interface{}) error {
str := i.(string)
strArr := attr["arr"].([]interface{})
found := false
if len(strArr) > 0 {
for _, item := range strArr {
itemStr := item.(string)
if itemStr == str {
found = true
}
}
}
if !found {
return fmt.Errorf("string not found in array")
}
return nil
})
}
You can get all the error-related information as a slice of strings. For formatting the messages, you can use pre-defined tags that will transform the message into the desired format provided:
%message
(error message from the validator)%target
(name of the field on which validation is performed)%validator
(name of the validator that is validating the field)%value
(value on which all the validators are validating)
Format example: validation error %message for %target with validation on %validator, provided: %value
package main
import (
"fmt"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
var schematics jsonschematics.Schematics
err := jsonschematics.LoadSchemaFromFile("path-to-your-schema.json")
if err != nil {
fmt.Println("String Array of Errors:", err.ExtractAsStrings("%message"))
} else {
fmt.Println("Schema Loaded Successfully")
}
}
package main
import (
"fmt"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
schema := jsonschematics.Schematics{
// Define your schema here
}
data := map[string]interface{}{
"Name": "John",
"Age": 30,
}
newData := schema.Operate(data)
fmt.Printf("Data after Operations: %v", newData)
}
You can also add your own functions to operate on the data:
package main
import (
"fmt"
"strings"
"github.com/ashbeelghouri/jsonschematics"
)
func main() {
var schematics jsonschematics.Schematics
err := schematics.LoadSchemaFromFile("path-to-your-schema.json")
if err != nil {
fmt.Println("Unable to load the schema:", err)
}
schematics.Operators.RegisterOperation("CapitalizeString", Capitalize)
}
func Capitalize(i interface{}, attributes map[string]interface{}) *interface{} {
str := i.(string)
var opResult interface{} = strings.ToUpper(string(str[0])) + strings.ToLower(str[1:])
return &opResult
}
type Schematics struct {
Schema Schema
Validators Validators
Operators Operators
Prefix string
Separator string
ArrayIdKey string
LoadSchemaFromFile(path string) error
LoadSchemaFromMap(m *map[string]interface{}) error
Validate(data interface{}) *ErrorMessages
Operate(data interface{}) interface{}
}
type Schema struct {
Version string `json:"version"`
Fields []Field `json:"fields"`
}
Version
is for the maintenance of the schemaFields
contains the validation logic for all the keys
type Field struct {
DependsOn []string `json:"depends_on"`
TargetKey string `json:"target_key"`
Description string `json:"description"`
Validators map[string]Constant `json:"validators"`
Operators map[string]Constant `json:"operators"`
}
DependsOn
will check if the keys in the array exist in the dataTargetKey
will target the value in the data through the keyDescription
can have anything to explain the data, this can also be emptyValidators
is an array map of validators where the name is the function name and the value contains attributes which is passed along to the function with the valueOperators
is an array map of operators where the name is the function name and the value contains attributes which is passed along to the function with the value
type Constant struct {
Attributes map[string]interface{} `json:"attributes"`
ErrMsg string `json:"err"`
}
Attributes
are passed into the validation function so it can have any map string interfaceErrMsg
is a string that is shown as an error when validation fails
type ErrorMessages struct {
Messages []ErrorMessage
AddError(validator string, target string, err string)
HaveErrors() bool
HaveSingleError(format string) error
}
type ErrorMessage struct {
Message string
Validator string
Target string
Value string
ID string
}
If you want to get the single error, you can define the error format like below: "validation error %message for %target on validating with %validator, provided: %value"
%validator
: this is the name of the validator%message
: this is the main error message%target
: this is the target key of the error message%value
: this is the value on which validation has been performed
Validate
function will always returnErrorMessages
Validator
is the function that has validated the valueTarget
is the key on which validation has been performedValue
is the target's valueID
is the target array key value to identify the validators inside the array, it is very important to define thearrayKeyID
, so we can identify which row of an array has the validation issues
String | Number | Date | Array |
---|---|---|---|
IsString | IsNumber | IsValidDate | ArrayLengthMax |
NotEmpty | MaxAllowed | IsLessThanNow | ArrayLengthMin |
StringTakenFromOptions | MinAllowed | IsMoreThanNow | StringsTakenFromOptions |
IsEmail | InBetween | IsBefore | |
MaxLengthAllowed | IsAfter | ||
MinLengthAllowed | IsInBetweenTime | ||
InBetweenLengthAllowed | |||
NoSpecialCharacters | |||
HaveSpecialCharacters | |||
LeastOneUpperCase | |||
LeastOneLowerCase | |||
LeastOneDigit | |||
IsURL | |||
IsNotURL | |||
HaveURLHostName | |||
HaveQueryParameter | |||
IsHttps | |||
IsURL | |||
LIKE | |||
MatchRegex |
v2 is the lates schema version designed. below are the required fields listed
fields <ARRAY> : [{
required <BOOLEAN>
depends_on <ARRAY OF STRINGS> : [] (can be empty)
target_key <STRING>
validators <ARRAY OF OBJ>: [{
name <STRING>
}]
operators <ARRAY OF OBJ>: [{
"name" <STRING>
}],
}]
target key is the key of an array in the data on which operations need to be performed, which can be anything and also can be a regex for the key in jsonschematics, we are flattening the object and creating keys with combination of the nested map[string]interface, in result we can get the keys like: map[string]value examples:
- user.profile.name
- user.*.profile.name (* will be replaced by \d+)
[NOTE] if the string for target is a valid regex then above * conversion wont happen as in regular expressions, the asterisk (*) is a quantifier that means "zero or more" of the preceding element
- Add the global data inside the schematics right before you are executing the validate function, as it will propogate to the attributes of the function
- You can add them in the main Schematics Object or add it to the schema file as well as if you want to keep the values from the data use "add_to_db" in schema file to add your value to attributes
Adding Data in Schematics
var s Schematics
Schematics.DB = map[string]interface{}{
"my-data":"valueofthedata"
}
Adding data globally in schema
{ "fields": [...],
"DB": {
"test": 22
}
}
Adding data from the json that is being validated
{ "fields": [{
"name":"field name"
"target_key":"user.data",
"add_to_db": true,
}],
"DB": {
"test": 22
}
}
Results Data will be propogated to the attributes map[string]interface{} like below under "DB" key.
constants.Attributes["DB"] = db
go 1.22.1
- Fork the repository on GitHub.
- Create a new branch for your feature or bug fix.
- Write tests to cover your changes.
- Update the documentation to include your features/changes.
- Add yourself to the contributors.
- Send a pull request.
This project is licensed under the MIT License. See the LICENSE file for details.
- Add more built-in validators and operators
- Improve documentation and examples
- Support for more complex data structures and validation rules