How to validate input unmarshal to a struct #58
-
Hi! So first, thank you for making this library, and I got a quick question about usage which I think I'm not getting. So suppose that I create a jsonschema struct like in the readme example: type MyStruct struct {
Amount float64 `json:"amount" default:"20" minimum:"10.5" example:"20.6" required:"true"`
Abc string `json:"abc" pattern:"[abc]"`
_ struct{} `additionalProperties:"false"` // Tags of unnamed field are applied to parent schema.
_ struct{} `title:"My Struct" description:"Holds my data."` // Multiple unnamed fields can be used.
} I use it to create a schema and that's all well and good. Now, I have a []byte input given by the user supposedly for data adhering to this schema. What I want to do is Validate that the data given is valid for the expected schema |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
This is possible with help of few extra libraries, please check this code snippet extracted from This additional processing has performance cost, so please make sure to run benchmarks if you plan to use it in performance-sensitive places. (I've slightly changed https://go.dev/play/p/ZqthI23bfwU package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/url"
"reflect"
"github.com/santhosh-tekuri/jsonschema/v3"
"github.com/swaggest/form/v5"
jsonschema2 "github.com/swaggest/jsonschema-go"
"github.com/swaggest/refl"
)
type MyStruct struct {
Amount float64 `json:"amount" default:"20" minimum:"10.5" example:"20.6"`
Abc string `json:"abc" pattern:"^[abc]+$" required:"true"`
_ struct{} `additionalProperties:"false"` // Tags of unnamed field are applied to parent schema.
_ struct{} `title:"My Struct" description:"Holds my data."` // Multiple unnamed fields can be used.
}
func main() {
decoder, err := NewValidatingUnmarshaler(MyStruct{})
if err != nil {
log.Fatal(err)
}
for _, payload := range []string{
`{"abc":"cabac"}`, // Default applied to Amount.
`{"abc":"eee"}`, // Pattern mismatch.
`{"abc":"abc", "amount":5.6}`, // Minimum mismatch.
`{"abc":"abc", "amount":23.4}`, // All good.
`{}`, // Missing required property.
} {
var ms MyStruct
if err := decoder.ValidateUnmarshal([]byte(payload), &ms); err != nil {
fmt.Printf("%s: decoding failed: %s\n", payload, err.Error())
} else {
fmt.Printf("%s: decoding succeeded: %+v\n", payload, ms)
}
}
// Output:
// {"abc":"cabac"}: decoding succeeded: {Amount:20 Abc:cabac _:{} _:{}}
// {"abc":"eee"}: decoding failed: validating payload: I[#/abc] S[#/properties/abc/pattern] does not match pattern "^[abc]+$"
// {"abc":"abc", "amount":5.6}: decoding failed: validating payload: I[#/amount] S[#/properties/amount/minimum] must be >= 21/2 but found 5.6
// {"abc":"abc", "amount":23.4}: decoding succeeded: {Amount:23.4 Abc:abc _:{} _:{}}
// {}: decoding failed: validating payload: I[#] S[#/required] missing properties: "abc"
}
// ValidatingUnmarshaler is a validating JSON decoder for a single struct type.
type ValidatingUnmarshaler struct {
defaults url.Values
defaultValDecoder *form.Decoder
schema *jsonschema.Schema
}
// NewValidatingUnmarshaler creates ValidatingUnmarshaler.
func NewValidatingUnmarshaler(structure interface{}) (*ValidatingUnmarshaler, error) {
var vu ValidatingUnmarshaler
r := jsonschema2.Reflector{}
schemaData, err := r.Reflect(structure)
if err != nil {
return nil, fmt.Errorf("reflecting schema: %w", err)
}
schemaBytes, err := json.Marshal(schemaData)
if err != nil {
return nil, fmt.Errorf("marshaling schema: %w", err)
}
compiler := jsonschema.NewCompiler()
if err := compiler.AddResource("schema.json", bytes.NewBuffer(schemaBytes)); err != nil {
return nil, fmt.Errorf("adding schema: %w", err)
}
if vu.schema, err = compiler.Compile("schema.json"); err != nil {
return nil, fmt.Errorf("compiling schema: %w", err)
}
vu.defaults = url.Values{}
refl.WalkTaggedFields(reflect.ValueOf(structure), func(v reflect.Value, sf reflect.StructField, tag string) {
vu.defaults[sf.Name] = []string{tag}
}, "default")
vu.defaultValDecoder = form.NewDecoder()
vu.defaultValDecoder.RegisterTagNameFunc(func(field reflect.StructField) string {
return field.Name
})
return &vu, nil
}
// ValidateUnmarshal applies defaults and validates payload before unmarshaling it.
func (vu *ValidatingUnmarshaler) ValidateUnmarshal(data []byte, v interface{}) error {
if err := vu.schema.Validate(bytes.NewReader(data)); err != nil {
return fmt.Errorf("validating payload: %w", err)
}
if err := vu.defaultValDecoder.Decode(v, vu.defaults); err != nil {
return fmt.Errorf("applying defaults: %w", err)
}
return json.Unmarshal(data, v)
} |
Beta Was this translation helpful? Give feedback.
This is possible with help of few extra libraries, please check this code snippet extracted from
github.com/swaggest/rest
codebase.This additional processing has performance cost, so please make sure to run benchmarks if you plan to use it in performance-sensitive places.
(I've slightly changed
MyStruct
so that there is no clash betweendefault
andrequired
.)https://go.dev/play/p/ZqthI23bfwU