golang快速实现JSON Schema表达式匹配插件库schema的使用

Golang快速实现JSON Schema表达式匹配插件库schema的使用

schema库可以更方便地检查map/array结构是否匹配特定模式,非常适合测试JSON API或验证传入请求的格式并向API用户提供错误消息。

安装

go get github.com/jgroeneveld/schema

基本使用示例

func TestJSON(t *testing.T) {
    // 假设这是从API获取的JSON响应
    reader := strings.NewReader(`{
        "id": 12,
        "name": "Max Mustermann",
        "age": 42,
        "height": 1.91,
        "footsize": 43,
        "address": {
            "street": "Main St",
            "zip": "12345"
        },
        "tags": ["red", "blue"]
    }`)

    // 定义schema匹配规则
    err := schema.MatchJSON(
        schema.Map{
            "id":       schema.IsInteger,  // 必须是整数
            "name":     "Max Mustermann",  // 必须完全匹配这个字符串
            "age":      42,                // 必须等于42
            "height":   schema.IsFloat,    // 必须是浮点数
            "footsize": schema.IsPresent,  // 必须存在这个字段
            "address": schema.Map{         // 嵌套map结构
                "street": schema.IsString, // 必须是字符串
                "zip":    schema.IsString, // 必须是字符串
            },
            "tags": schema.ArrayIncluding("red"), // 数组必须包含"red"
        },
        reader,
    )

    if err != nil {
        t.Fatal(err)
    }
}

主要入口点

// 匹配任意数据结构
schema.Match(schema.Matcher, interface{}) error

// 直接从JSON Reader匹配
schema.MatchJSON(schema.Matcher, io.Reader) error

常用匹配器

  1. 具体值:任何具体值如"Name", 12, true, false, nil

  2. IsPresent:值是否存在(空字符串也算存在)

  3. 类型检查

    • IsString
    • IsInt
    • IsFloat
    • IsBool
    • IsTime(format)
  4. Map匹配器

    // 必须完全匹配所有键和值,不允许额外键
    schema.Map{"key": Matcher, ...}
    
    // 只检查给定的键和值,忽略额外键
    schema.MapIncluding{"key": Matcher, ...}
    
  5. 数组匹配器

    // 按顺序匹配所有数组元素
    schema.Array(Matcher...)
    
    // 匹配所有数组元素但忽略顺序
    schema.ArrayUnordered(Matcher...)
    
    // 报告无法匹配的元素
    schema.ArrayIncluding(Matcher...)
    
    // 每个数组元素都必须匹配给定的匹配器
    schema.ArrayEach(Matcher)
    

自定义匹配器

你可以实现自己的匹配器:

// 检查RFC3339格式的时间字符串
var IsTime = schema.MatcherFunc("IsTime",
    func(data interface{}) *schema.Error {
        s, ok := data.(string)
        if !ok {
            return schema.SelfError("不是有效时间: 不是字符串")
        }

        _, err := time.Parse(time.RFC3339, s)
        if err != nil {
            return schema.SelfError("不是有效时间: " + err.Error())
        }
        return nil
    },
)

// 更通用的时间格式匹配器
func IsTime(format string) schema.Matcher {
    return schema.MatcherFunc("IsTime",
        func(data interface{}) *schema.Error {
            s, ok := data.(string)
            if !ok {
                return schema.SelfError("不是有效时间: 不是字符串")
            }

            _, err := time.Parse(format, s)
            if err != nil {
                return schema.SelfError("不是有效时间: " + err.Error())
            }
            return nil
        },
    )
}

错误处理

当匹配失败时,schema会返回详细的错误信息。例如对于以下JSON输入:

{
    "id": 12,
    "name": "Hans Meier",
    "age": 42,
    "height": 1.91,
    "address": {
        "street": 12
    },
    "tags": ["blue", "green"]
}

错误输出会是:

"address": Missing keys: "zip"
"address.street": is no string but float64
"name": "Hans Meier" != "Max Mustermann"
"tags": red:string(0) not included
Missing keys: "footsize"

注意事项

  1. 数字处理:JSON不区分整数和浮点数,Go的JSON库在解析到interface{}时总是返回float64,这可能导致一些边界情况。

  2. 数组匹配器:数组匹配器会先匹配具体值,再匹配通用匹配器,最后匹配IsPresent,这种顺序可能在某些复杂嵌套结构中不适用。

schema库提供了简单直观的方式来验证JSON结构,特别适合API测试和请求验证场景。


更多关于golang快速实现JSON Schema表达式匹配插件库schema的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang快速实现JSON Schema表达式匹配插件库schema的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang快速实现JSON Schema表达式匹配

JSON Schema是一种用于描述JSON数据结构的强大工具,可以用来验证JSON文档的结构。在Golang中,我们可以使用github.com/xeipuuv/gojsonschema库来快速实现JSON Schema的表达式匹配。

安装依赖

首先安装必要的依赖:

go get github.com/xeipuuv/gojsonschema

基本使用示例

下面是一个基本的JSON Schema验证示例:

package main

import (
	"fmt"
	"log"

	"github.com/xeipuuv/gojsonschema"
)

func main() {
	// 定义JSON Schema
	schemaString := `{
		"type": "object",
		"properties": {
			"name": {
				"type": "string",
				"minLength": 3,
				"maxLength": 20
			},
			"age": {
				"type": "integer",
				"minimum": 18,
				"maximum": 120
			},
			"email": {
				"type": "string",
				"format": "email"
			}
		},
		"required": ["name", "age"]
	}`

	// 要验证的JSON数据
	documentString := `{
		"name": "John Doe",
		"age": 30,
		"email": "john@example.com"
	}`

	// 加载schema
	schemaLoader := gojsonschema.NewStringLoader(schemaString)
	documentLoader := gojsonschema.NewStringLoader(documentString)

	// 验证
	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		log.Fatalf("Error validating schema: %v", err)
	}

	if result.Valid() {
		fmt.Println("The document is valid!")
	} else {
		fmt.Println("The document is not valid. See errors:")
		for _, desc := range result.Errors() {
			fmt.Printf("- %s\n", desc)
		}
	}
}

高级功能

1. 自定义验证器

package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/xeipuuv/gojsonschema"
)

// 自定义格式验证器
func init() {
	gojsonschema.FormatCheckers.Add("uppercase", UppercaseFormatChecker{})
}

type UppercaseFormatChecker struct{}

func (c UppercaseFormatChecker) IsFormat(input interface{}) bool {
	s, ok := input.(string)
	if !ok {
		return false
	}
	return s == strings.ToUpper(s)
}

func main() {
	schemaString := `{
		"type": "object",
		"properties": {
			"username": {
				"type": "string",
				"format": "uppercase"
			}
		}
	}`

	documentString := `{"username": "ADMIN"}`

	schemaLoader := gojsonschema.NewStringLoader(schemaString)
	documentLoader := gojsonschema.NewStringLoader(documentString)

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		log.Fatal(err)
	}

	if result.Valid() {
		fmt.Println("Valid!")
	} else {
		fmt.Println("Invalid:")
		for _, err := range result.Errors() {
			fmt.Println(err.String())
		}
	}
}

2. 从文件加载Schema

package main

import (
	"fmt"
	"log"

	"github.com/xeipuuv/gojsonschema"
)

func main() {
	// 从文件加载schema
	schemaLoader := gojsonschema.NewReferenceLoader("file:///path/to/schema.json")
	documentLoader := gojsonschema.NewReferenceLoader("file:///path/to/document.json")

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		log.Fatal(err)
	}

	if result.Valid() {
		fmt.Println("Valid!")
	} else {
		fmt.Println("Invalid:")
		for _, err := range result.Errors() {
			fmt.Println(err.String())
		}
	}
}

3. 动态生成Schema

package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/xeipuuv/gojsonschema"
)

func main() {
	// 动态生成schema
	schema := map[string]interface{}{
		"type": "object",
		"properties": map[string]interface{}{
			"id": map[string]interface{}{
				"type":    "string",
				"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$",
			},
			"tags": map[string]interface{}{
				"type":  "array",
				"items": map[string]interface{}{"type": "string"},
				"minItems": 1,
				"uniqueItems": true,
			},
		},
		"required": []string{"id"},
	}

	schemaBytes, err := json.Marshal(schema)
	if err != nil {
		log.Fatal(err)
	}

	documentString := `{
		"id": "550e8400-e29b-41d4-a716-446655440000",
		"tags": ["golang", "json"]
	}`

	schemaLoader := gojsonschema.NewBytesLoader(schemaBytes)
	documentLoader := gojsonschema.NewStringLoader(documentString)

	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
	if err != nil {
		log.Fatal(err)
	}

	if result.Valid() {
		fmt.Println("Valid!")
	} else {
		fmt.Println("Invalid:")
		for _, err := range result.Errors() {
			fmt.Println(err.String())
		}
	}
}

性能优化建议

  1. 缓存Schema加载器:如果多次使用同一个schema,可以缓存gojsonschema.Schema对象
  2. 避免重复解析:对于频繁验证的场景,可以预编译schema
  3. 使用ReferenceLoader:对于大型schema,使用文件引用而不是字符串

总结

gojsonschema库提供了强大的JSON Schema验证功能,支持:

  • 基本数据类型验证
  • 复杂结构验证(数组、对象等)
  • 自定义验证规则
  • 多种加载方式(字符串、文件、网络等)

通过合理使用这个库,可以轻松实现JSON数据的结构化验证,确保数据符合预期的格式和约束。

回到顶部