Add jsonschema Validate

This commit is contained in:
George Suntres
2026-03-29 12:01:48 -04:00
parent ebdf370c23
commit 91bb0ac22f
6 changed files with 164 additions and 3 deletions

34
check_valid.go Normal file
View File

@@ -0,0 +1,34 @@
package commons
import (
"encoding/json"
"log"
"github.com/ianlancetaylor/jsonschema"
"github.com/ianlancetaylor/jsonschema/draft7"
)
func Validate(schema string, dataAny any) error {
data := StructToMapRecursive(dataAny)
var v any
if err := json.Unmarshal([]byte(schema), &v); err != nil {
log.Printf("Failed to decode json %#v", err)
return err
}
vldtor, err := jsonschema.SchemaFromJSON(draft7.SchemaID, nil, v)
if err != nil {
return err
}
// validate
valid := vldtor.Validate(data)
if valid != nil {
return valid
}
return nil
}

46
check_valid_test.go Normal file
View File

@@ -0,0 +1,46 @@
package commons
import (
"testing"
)
const sampleSchema = `
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"age": {
"type": "number"
}
},
"required": ["id", "name"]
}
`
func TestRequiredName (t *testing.T) {
data := map[string]any {
"id": "myid",
"age": 100,
}
if err := Validate(sampleSchema, data); err == nil || err.Error() != `required/name: missing required field "name"` {
t.Fatalf("Should throw `%s` but got %s", "required/name: missing required field \"name\"", err)
}
}
func TestPassingValidate (t *testing.T) {
data := map[string]any {
"id": "myid",
"name": "Hey",
"age": 100,
}
if err := Validate(sampleSchema, data); err != nil {
t.Fatal("Should have not thrown an error")
}
}

5
go.mod
View File

@@ -2,4 +2,7 @@ module git.gsuntres.com/general/commons
go 1.25.0 go 1.25.0
require go.mongodb.org/mongo-driver/v2 v2.5.0 require (
github.com/ianlancetaylor/jsonschema v0.0.0-20251021232724-46ecbf32a0a5
go.mongodb.org/mongo-driver/v2 v2.5.0
)

2
go.sum
View File

@@ -2,5 +2,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/ianlancetaylor/jsonschema v0.0.0-20251021232724-46ecbf32a0a5 h1:x2QxKV4w/sBEwwUUmBH/8cFjeOBZqwaB5dz5rcuFspU=
github.com/ianlancetaylor/jsonschema v0.0.0-20251021232724-46ecbf32a0a5/go.mod h1:KtN3dTgXsLnC5GJBRNmOPd/HUInNcQ84lUCrKJPrvDc=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=

View File

@@ -4,10 +4,50 @@ import (
"log" "log"
"reflect" "reflect"
"strings" "strings"
// "encoding/json"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
) )
// StructToMapRecursive given a struct or a primitive will return the equivalent
// map[string]any of the struct or the primitive as is.
func StructToMapRecursive(obj any) any {
v := reflect.ValueOf(obj)
// Handle pointers by getting the underlying element
if v.Kind() == reflect.Ptr {
if v.IsNil() { return nil }
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
result := make(map[string]any)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
// Skip unexported fields (private fields)
if field.PkgPath != "" { continue }
// Recurse into the field's value
result[field.Name] = StructToMapRecursive(v.Field(i).Interface())
}
return result
case reflect.Slice, reflect.Array:
result := make([]any, v.Len())
for i := 0; i < v.Len(); i++ {
result[i] = StructToMapRecursive(v.Index(i).Interface())
}
return result
default:
// Return basic types (int, string, etc.) as is
return obj
}
}
// MapToStruct will convert a map[string]any to a struct. // MapToStruct will convert a map[string]any to a struct.
func MapToStruct(m map[string]any, o any) error { func MapToStruct(m map[string]any, o any) error {
b, err := bson.Marshal(m) b, err := bson.Marshal(m)

View File

@@ -4,6 +4,42 @@ import (
"testing" "testing"
) )
func TestStructToMapRecursive(t *testing.T) {
type O struct {
Name string
Age int
Active bool
}
o := &O{
Name: "Nick",
Age: 15,
Active: true,
}
mAny := StructToMapRecursive(o)
switch mAny.(type) {
case map[string]any:
m := mAny.(map[string]any)
if v, ok := m["Name"]; !ok || v != "Nick" {
t.Fatalf("Unexpected map %v", v)
}
if v, ok := m["Age"]; !ok || v != 15 {
t.Fatalf("Unexpected map %v", v)
}
if v, ok := m["Active"]; !ok || v != true {
t.Fatalf("Unexpected map %v", v)
}
default:
t.Fatal("is not map")
}
}
func TestMapToStruct(t *testing.T) { func TestMapToStruct(t *testing.T) {
o := map[string]any { o := map[string]any {
"name": "name1", "name": "name1",