Skip to content

Validate OR Change the JSON Data With JSON Schema in Golang

License

Notifications You must be signed in to change notification settings

ashbeelghouri/jsonschematics

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jsonschematics

jsonschematics is a Go package designed to validate and manipulate JSON data structures using schematics.

Features

  • Validate JSON objects against defined schematics
  • Convert schematics to JSON schemas
  • Handle complex data validation scenarios
  • Perform operations on the data

Installation

To install the package, use the following command:

go get github.com/ashbeelghouri/jsonschematics@latest

Usage

Validation

Validating JSON Data

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")
    }
}

Loading Schematics From JSON File

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")
    }
}

Loading Schematics From map[string]interface{}

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")
    }
}

Adding Custom Validation Functions

You can also add your own functions to validate the data:

Example 1
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
}
Example 2
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
    })
}

Get Error Messages as a String Slice

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")
    }
}

Operations

Perform Operations on Object

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)
}

Adding Custom Operator Functions

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
}

API Reference

Example JSON Files

Structs

Schematics

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{}
}
Schema
type Schema struct {
    Version string `json:"version"`
    Fields  []Field `json:"fields"`
}
Explanation
  • Version is for the maintenance of the schema
  • Fields contains the validation logic for all the keys
Field
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"`
}
Explanation
  • DependsOn will check if the keys in the array exist in the data
  • TargetKey will target the value in the data through the key
  • Description can have anything to explain the data, this can also be empty
  • Validators 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 value
  • Operators 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
Constant
type Constant struct {
    Attributes map[string]interface{} `json:"attributes"`
    ErrMsg      string                `json:"err"`
}
Explanation
  • Attributes are passed into the validation function so it can have any map string interface
  • ErrMsg is a string that is shown as an error when validation fails

Errors

ErrorMessages
type ErrorMessages struct {
    Messages []ErrorMessage
    AddError(validator string, target string, err string)
    HaveErrors() bool
    HaveSingleError(format string) error
}
ErrorMessage
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
Explanation
  • Validate function will always return ErrorMessages
  • Validator is the function that has validated the value
  • Target is the key on which validation has been performed
  • Value is the target's value
  • ID is the target array key value to identify the validators inside the array, it is very important to define the arrayKeyID, so we can identify which row of an array has the validation issues

List of Basic Validators

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

Schema

v2@latest

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 Keys

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 Custom Data

  • 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
Example 1

Adding Data in Schematics

var s Schematics
Schematics.DB = map[string]interface{}{
    "my-data":"valueofthedata"
}
Example 2

Adding data globally in schema

{   "fields": [...],
    "DB": {
        "test": 22
      }
}
Example 3

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 Version

go 1.22.1

Contributing

  1. Fork the repository on GitHub.
  2. Create a new branch for your feature or bug fix.
  3. Write tests to cover your changes.
  4. Update the documentation to include your features/changes.
  5. Add yourself to the contributors.
  6. Send a pull request.

Contributors

Ashbeel Ghouri

Amadou Diallo

License

This project is licensed under the MIT License. See the LICENSE file for details.

Future Plans

  • Add more built-in validators and operators
  • Improve documentation and examples
  • Support for more complex data structures and validation rules