Skip to content

数据验证

FxJSON 提供了强大的数据验证功能,包括内置验证器和自定义验证规则,帮助您确保数据的完整性和正确性。

注意: FxJSON 提供了内置的 node.Validate(validator) 方法(详见 API 文档),本指南中的示例展示了如何实现自定义验证逻辑以满足特定需求。

内置验证器

基础类型验证

go
func basicValidation() {
    jsonData := `{
        "email": "user@example.com",
        "website": "https://example.com",
        "ip": "192.168.1.1",
        "uuid": "123e4567-e89b-12d3-a456-426614174000",
        "phone": "+86-13800138000",
        "age": 25,
        "score": 95.5
    }`

    node := fxjson.FromBytes([]byte(jsonData))

    // 邮箱验证
    if node.Get("email").IsValidEmail() {
        fmt.Println("✅ 邮箱格式正确")
    } else {
        fmt.Println("❌ 邮箱格式错误")
    }

    // URL验证
    if node.Get("website").IsValidURL() {
        fmt.Println("✅ URL格式正确")
    }

    // IP地址验证
    if node.Get("ip").IsValidIP() {
        fmt.Println("✅ IP地址格式正确")
    }

    // UUID验证
    if node.Get("uuid").IsValidUUID() {
        fmt.Println("✅ UUID格式正确")
    }

    // 数字范围验证
    age := node.Get("age").IntOr(0)
    if age >= 0 && age <= 150 {
        fmt.Println("✅ 年龄在有效范围内")
    }
}

字符串验证

go
func stringValidation() {
    jsonData := `{
        "username": "user123",
        "password": "SecurePass123!",
        "code": "ABC123",
        "description": "这是一个描述文本"
    }`

    node := fxjson.FromBytes([]byte(jsonData))

    // 字符串长度验证
    username := node.Get("username").StringOr("")
    if len(username) >= 3 && len(username) <= 20 {
        fmt.Println("✅ 用户名长度符合要求")
    }

    // 密码强度验证
    password := node.Get("password").StringOr("")
    if validatePasswordStrength(password) {
        fmt.Println("✅ 密码强度符合要求")
    }

    // 字符模式验证
    code := node.Get("code").StringOr("")
    if validateAlphanumeric(code) {
        fmt.Println("✅ 验证码格式正确")
    }
}

func validatePasswordStrength(password string) bool {
    if len(password) < 8 {
        return false
    }
    
    hasUpper := false
    hasLower := false
    hasDigit := false
    hasSpecial := false
    
    for _, char := range password {
        switch {
        case char >= 'A' && char <= 'Z':
            hasUpper = true
        case char >= 'a' && char <= 'z':
            hasLower = true
        case char >= '0' && char <= '9':
            hasDigit = true
        default:
            hasSpecial = true
        }
    }
    
    return hasUpper && hasLower && hasDigit && hasSpecial
}

func validateAlphanumeric(s string) bool {
    for _, char := range s {
        if !((char >= 'A' && char <= 'Z') || 
             (char >= 'a' && char <= 'z') || 
             (char >= '0' && char <= '9')) {
            return false
        }
    }
    return len(s) > 0
}

自定义验证规则

验证规则定义

go
type ValidationRule struct {
    Required     bool                          `json:"required"`
    Type         string                        `json:"type"`
    MinLength    int                           `json:"min_length"`
    MaxLength    int                           `json:"max_length"`
    Min          float64                       `json:"min"`
    Max          float64                       `json:"max"`
    Pattern      string                        `json:"pattern"`
    Default      interface{}                   `json:"default"`
    Enum         []interface{}                 `json:"enum"`
    CustomCheck  func(interface{}) bool        `json:"-"`
    ErrorMessage string                        `json:"error_message"`
}

type DataValidator struct {
    Rules map[string]ValidationRule `json:"rules"`
}

func customValidation() {
    // 定义验证规则
    validator := &DataValidator{
        Rules: map[string]ValidationRule{
            "name": {
                Required:     true,
                Type:         "string",
                MinLength:    2,
                MaxLength:    50,
                ErrorMessage: "姓名必须是2-50个字符",
            },
            "email": {
                Required:     true,
                Type:         "string",
                Pattern:      `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`,
                ErrorMessage: "请提供有效的邮箱地址",
            },
            "age": {
                Required:     true,
                Type:         "number",
                Min:          0,
                Max:          150,
                ErrorMessage: "年龄必须在0-150之间",
            },
            "gender": {
                Required:     false,
                Type:         "string",
                Enum:         []interface{}{"男", "女", "其他"},
                Default:      "未指定",
                ErrorMessage: "性别必须是:男、女、其他",
            },
            "phone": {
                Required: false,
                Type:     "string",
                CustomCheck: func(value interface{}) bool {
                    if str, ok := value.(string); ok {
                        return validateChinesePhone(str)
                    }
                    return false
                },
                ErrorMessage: "请提供有效的中国手机号码",
            },
        },
    }

    // 测试数据
    testData := `{
        "name": "张三",
        "email": "zhangsan@example.com",
        "age": 28,
        "gender": "男",
        "phone": "13800138000"
    }`

    node := fxjson.FromBytes([]byte(testData)
    
    // 执行验证(使用自定义验证器)
    errors := validator.Validate(node)
    if len(errors) == 0 {
        fmt.Println("✅ 所有数据验证通过")
    } else {
        fmt.Println("❌ 验证失败:")
        for _, err := range errors {
            fmt.Printf("  - %s\n", err)
        }
    }
}

func validateChinesePhone(phone string) bool {
    if len(phone) != 11 {
        return false
    }
    
    if phone[0] != '1' {
        return false
    }
    
    validSecondDigits := []byte{'3', '4', '5', '6', '7', '8', '9'}
    secondDigit := phone[1]
    
    for _, valid := range validSecondDigits {
        if secondDigit == valid {
            for i := 2; i < 11; i++ {
                if phone[i] < '0' || phone[i] > '9' {
                    return false
                }
            }
            return true
        }
    }
    
    return false
}

// DataValidator 的 Validate 方法实现
func (dv *DataValidator) Validate(node fxjson.Node) []string {
    var errors []string
    
    for fieldName, rule := range dv.Rules {
        fieldNode := node.Get(fieldName)
        
        // 检查必需字段
        if rule.Required && !fieldNode.Exists() {
            errors = append(errors, fmt.Sprintf("%s: 字段是必需的", fieldName))
            continue
        }
        
        // 如果字段不存在且不是必需的,使用默认值
        if !fieldNode.Exists() {
            if rule.Default != nil {
                continue // 有默认值,跳过验证
            }
            continue // 可选字段且无默认值,跳过验证
        }
        
        // 类型验证
        if !dv.validateType(fieldNode, rule.Type) {
            errors = append(errors, fmt.Sprintf("%s: %s", fieldName, rule.ErrorMessage))
            continue
        }
        
        // 具体验证
        if err := dv.validateField(fieldNode, rule, fieldName); err != nil {
            errors = append(errors, err.Error())
        }
    }
    
    return errors
}

func (dv *DataValidator) validateType(node fxjson.Node, expectedType string) bool {
    switch expectedType {
    case "string":
        return node.IsString()
    case "number":
        return node.IsNumber()
    case "boolean":
        return node.IsBool()
    case "array":
        return node.IsArray()
    case "object":
        return node.IsObject()
    default:
        return true
    }
}

func (dv *DataValidator) validateField(node fxjson.Node, rule ValidationRule, fieldName string) error {
    // 字符串验证
    if rule.Type == "string" {
        str := node.StringOr("")
        
        if rule.MinLength > 0 && len(str) < rule.MinLength {
            return fmt.Errorf("%s: %s", fieldName, rule.ErrorMessage)
        }
        
        if rule.MaxLength > 0 && len(str) > rule.MaxLength {
            return fmt.Errorf("%s: %s", fieldName, rule.ErrorMessage)
        }
        
        if rule.Pattern != "" {
            matched, _ := regexp.MatchString(rule.Pattern, str)
            if !matched {
                return fmt.Errorf("%s: %s", fieldName, rule.ErrorMessage)
            }
        }
        
        if len(rule.Enum) > 0 {
            valid := false
            for _, enum := range rule.Enum {
                if str == enum {
                    valid = true
                    break
                }
            }
            if !valid {
                return fmt.Errorf("%s: %s", fieldName, rule.ErrorMessage)
            }
        }
    }
    
    // 数字验证
    if rule.Type == "number" {
        num := node.FloatOr(0)
        
        if rule.Min != 0 && num < rule.Min {
            return fmt.Errorf("%s: %s", fieldName, rule.ErrorMessage)
        }
        
        if rule.Max != 0 && num > rule.Max {
            return fmt.Errorf("%s: %s", fieldName, rule.ErrorMessage)
        }
    }
    
    // 自定义验证
    if rule.CustomCheck != nil {
        var value interface{}
        switch rule.Type {
        case "string":
            value = node.StringOr("")
        case "number":
            value = node.FloatOr(0)
        case "boolean":
            value = node.BoolOr(false)
        default:
            value = node.StringOr("")
        }
        
        if !rule.CustomCheck(value) {
            return fmt.Errorf("%s: %s", fieldName, rule.ErrorMessage)
        }
    }
    
    return nil
}

复杂数据结构验证

嵌套对象验证

go
func nestedValidation() {
    userSchema := &DataValidator{
        Rules: map[string]ValidationRule{
            "personal.name": {
                Required:  true,
                Type:      "string",
                MinLength: 2,
                MaxLength: 50,
            },
            "personal.age": {
                Required: true,
                Type:     "number",
                Min:      0,
                Max:      150,
            },
            "contact.email": {
                Required: true,
                Type:     "string",
                Pattern:  `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`,
            },
            "contact.phone": {
                Required: false,
                Type:     "string",
                CustomCheck: func(value interface{}) bool {
                    if str, ok := value.(string); ok {
                        return validateChinesePhone(str)
                    }
                    return false
                },
            },
        },
    }

    testData := `{
        "personal": {
            "name": "张三",
            "age": 28
        },
        "contact": {
            "email": "zhangsan@example.com",
            "phone": "13800138000"
        }
    }`

    node := fxjson.FromBytes([]byte(testData)
    
    errors := validateNested(node, userSchema)
    if len(errors) == 0 {
        fmt.Println("✅ 嵌套数据验证通过")
    } else {
        fmt.Println("❌ 嵌套数据验证失败:")
        for _, err := range errors {
            fmt.Printf("  - %s\n", err)
        }
    }
}

func validateNested(node fxjson.Node, validator *DataValidator) []string {
    var errors []string
    
    for fieldPath, rule := range validator.Rules {
        fieldNode := node.GetPath(fieldPath)
        
        if rule.Required && !fieldNode.Exists() {
            errors = append(errors, fmt.Sprintf("%s: 字段是必需的", fieldPath))
            continue
        }
        
        if !fieldNode.Exists() {
            continue
        }
        
        if !validator.validateType(fieldNode, rule.Type) {
            errors = append(errors, fmt.Sprintf("%s: 类型不匹配", fieldPath))
            continue
        }
        
        if err := validator.validateField(fieldNode, rule, fieldPath); err != nil {
            errors = append(errors, err.Error())
        }
    }
    
    return errors
}

数组验证

go
func arrayValidation() {
    arraySchema := &DataValidator{
        Rules: map[string]ValidationRule{
            "users": {
                Required: true,
                Type:     "array",
                CustomCheck: func(value interface{}) bool {
                    // 验证数组长度
                    return true
                },
            },
        },
    }

    itemSchema := &DataValidator{
        Rules: map[string]ValidationRule{
            "name": {
                Required:  true,
                Type:      "string",
                MinLength: 2,
                MaxLength: 50,
            },
            "email": {
                Required: true,
                Type:     "string",
                Pattern:  `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`,
            },
        },
    }

    testData := `{
        "users": [
            {"name": "张三", "email": "zhang@example.com"},
            {"name": "李四", "email": "li@example.com"},
            {"name": "王五", "email": "invalid-email"}
        ]
    }`

    node := fxjson.FromBytes([]byte(testData)
    
    // 验证数组本身
    errors := arraySchema.Validate(node)
    
    // 验证数组元素
    users := node.Get("users")
    if users.IsArray() {
        users.ArrayForEach(func(index int, userNode fxjson.Node) bool {
            itemErrors := itemSchema.Validate(userNode)
            for _, err := range itemErrors {
                errors = append(errors, fmt.Sprintf("users[%d].%s", index, err))
            }
            return true
        })
    }
    
    if len(errors) == 0 {
        fmt.Println("✅ 数组数据验证通过")
    } else {
        fmt.Println("❌ 数组数据验证失败:")
        for _, err := range errors {
            fmt.Printf("  - %s\n", err)
        }
    }
}

数据清理和转换

数据清理

go
func dataSanitization() {
    dirtyData := `{
        "name": "  张三  ",
        "email": "ZHANGSAN@EXAMPLE.COM",
        "phone": "138-0013-8000",
        "description": "<script>alert('xss')</script>正常内容",
        "tags": ["  Go  ", "JSON", "  Performance  "]
    }`

    node := fxjson.FromBytes([]byte(dirtyData)
    
    // 创建清理后的数据
    cleaned := map[string]interface{}{
        "name":        strings.TrimSpace(node.Get("name").StringOr("")),
        "email":       strings.ToLower(node.Get("email").StringOr("")),
        "phone":       sanitizePhone(node.Get("phone").StringOr("")),
        "description": sanitizeHTML(node.Get("description").StringOr("")),
        "tags":        sanitizeTags(node.Get("tags")),
    }

    // 输出清理结果
    for key, value := range cleaned {
        fmt.Printf("%s: %v\n", key, value)
    }
}

func sanitizePhone(phone string) string {
    // 移除所有非数字字符
    result := ""
    for _, char := range phone {
        if char >= '0' && char <= '9' {
            result += string(char)
        }
    }
    return result
}

func sanitizeHTML(input string) string {
    // 简单的HTML标签移除(实际使用中建议使用专门的库)
    re := regexp.MustCompile(`<[^>]*>`)
    return re.ReplaceAllString(input, "")
}

func sanitizeTags(tagsNode fxjson.Node) []string {
    var tags []string
    if tagsNode.IsArray() {
        tagsNode.ArrayForEach(func(index int, tag fxjson.Node) bool {
            cleaned := strings.TrimSpace(tag.StringOr(""))
            if cleaned != "" {
                tags = append(tags, cleaned)
            }
            return true
        })
    }
    return tags
}

数据转换和标准化

go
func dataTransformation() {
    rawData := `{
        "user_name": "zhang_san",
        "user_email": "ZHANG@EXAMPLE.COM",
        "user_age": "28",
        "is_active": "true",
        "created_date": "2024-01-15"
    }`

    node := fxjson.FromBytes([]byte(rawData)
    
    // 转换为标准格式
    standardized := map[string]interface{}{
        "name":      toCamelCase(node.Get("user_name").StringOr("")),
        "email":     strings.ToLower(node.Get("user_email").StringOr("")),
        "age":       node.Get("user_age").IntOr(0),
        "isActive":  node.Get("is_active").StringOr("") == "true",
        "createdAt": formatDate(node.Get("created_date").StringOr("")),
    }

    fmt.Println("标准化后的数据:")
    for key, value := range standardized {
        fmt.Printf("%s: %v (%T)\n", key, value, value)
    }
}

func toCamelCase(snake_case string) string {
    parts := strings.Split(snake_case, "_")
    if len(parts) <= 1 {
        return snake_case
    }
    
    result := parts[0]
    for i := 1; i < len(parts); i++ {
        if len(parts[i]) > 0 {
            result += strings.ToUpper(parts[i][:1]) + parts[i][1:]
        }
    }
    return result
}

func formatDate(dateStr string) string {
    // 简单的日期格式转换
    if len(dateStr) == 10 && strings.Count(dateStr, "-") == 2 {
        return dateStr + "T00:00:00Z"
    }
    return dateStr
}

批量验证

go
func batchValidation() {
    // 批量验证多个用户数据
    usersData := `[
        {"name": "张三", "email": "zhang@example.com", "age": 28},
        {"name": "李四", "email": "li@example.com", "age": 25},
        {"name": "", "email": "invalid", "age": 200},
        {"name": "王五", "email": "wang@example.com", "age": 30}
    ]`

    schema := &DataValidator{
        Rules: map[string]ValidationRule{
            "name": {
                Required:  true,
                Type:      "string",
                MinLength: 1,
                MaxLength: 50,
            },
            "email": {
                Required: true,
                Type:     "string",
                Pattern:  `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`,
            },
            "age": {
                Required: true,
                Type:     "number",
                Min:      0,
                Max:      150,
            },
        },
    }

    node := fxjson.FromBytes([]byte(usersData)
    
    var allErrors []string
    validCount := 0
    totalCount := 0

    node.ArrayForEach(func(index int, userNode fxjson.Node) bool {
        totalCount++
        errors := schema.Validate(userNode)
        
        if len(errors) == 0 {
            validCount++
            fmt.Printf("用户 %d: ✅ 验证通过\n", index+1)
        } else {
            fmt.Printf("用户 %d: ❌ 验证失败\n", index+1)
            for _, err := range errors {
                errorMsg := fmt.Sprintf("  用户%d - %s", index+1, err)
                fmt.Println(errorMsg)
                allErrors = append(allErrors, errorMsg)
            }
        }
        return true
    })

    fmt.Printf("\n批量验证结果: %d/%d 通过\n", validCount, totalCount)
    if len(allErrors) > 0 {
        fmt.Printf("总共 %d 个错误\n", len(allErrors))
    }
}

性能优化

go
func optimizedValidation() {
    // 对于大量数据,使用并发验证
    largeDataset := generateLargeUserDataset(10000)
    
    schema := &DataValidator{
        Rules: map[string]ValidationRule{
            "name":  {Required: true, Type: "string", MinLength: 1},
            "email": {Required: true, Type: "string", Pattern: `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`},
            "age":   {Required: true, Type: "number", Min: 0, Max: 150},
        },
    }

    start := time.Now()
    
    // 并发验证
    var wg sync.WaitGroup
    errorsChan := make(chan string, 100)
    validCount := int64(0)
    
    node := fxjson.FromBytes([]byte(largeDataset)
    node.ArrayForEach(func(index int, userNode fxjson.Node) bool {
        wg.Add(1)
        go func(idx int, user fxjson.Node) {
            defer wg.Done()
            
            errors := schema.Validate(user)
            if len(errors) == 0 {
                atomic.AddInt64(&validCount, 1)
            } else {
                for _, err := range errors {
                    select {
                    case errorsChan <- fmt.Sprintf("用户%d: %s", idx+1, err):
                    default:
                        // 错误通道满,跳过
                    }
                }
            }
        }(index, userNode)
        
        return true
    })
    
    wg.Wait()
    close(errorsChan)
    
    duration := time.Since(start)
    
    var errorCount int
    for range errorsChan {
        errorCount++
    }
    
    fmt.Printf("并发验证完成: 耗时 %v\n", duration)
    fmt.Printf("有效数据: %d, 错误数据: %d\n", validCount, errorCount)
}

func generateLargeUserDataset(count int) string {
    var users []string
    for i := 0; i < count; i++ {
        user := fmt.Sprintf(`{"name": "用户%d", "email": "user%d@example.com", "age": %d}`,
            i+1, i+1, 20+i%60)
        users = append(users, user)
    }
    return "[" + strings.Join(users, ",") + "]"
}

数据验证是确保应用程序数据质量的重要环节。FxJSON 的验证功能帮助您在数据处理的早期阶段发现和修正问题,提高应用程序的可靠性和安全性。

基于 MIT 许可证发布