- 新增 Lookuper 接口,提供 Lookup 和 MustLookup 两种路径查找方式 - Accessor 接口继承 Lookuper,保持向后兼容 - MustLookup 在路径不存在时返回 Nil 访问器,简化调用代码 - 更新 fieldx.Schema 使用 Lookuper 接口,支持更灵活的数据源 - 添加 Required 字段选项,控制字段不存在时的行为
185 lines
4.7 KiB
Go
185 lines
4.7 KiB
Go
package fieldx
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"git.fsdpf.net/go/reflux"
|
|
"git.fsdpf.net/go/reflux/valuex"
|
|
)
|
|
|
|
// FieldType 定义字段类型
|
|
type FieldType string
|
|
|
|
const (
|
|
// FieldTypeString 固定字符串值
|
|
FieldTypeString FieldType = "string"
|
|
// FieldTypeField 从传入对象中获取字段值
|
|
FieldTypeField FieldType = "field"
|
|
// FieldTypeObject 嵌套对象类型
|
|
FieldTypeObject FieldType = "object"
|
|
)
|
|
|
|
// Field 定义单个字段的 Schema
|
|
type Field struct {
|
|
// Type 字段类型: string, field, object
|
|
Type FieldType `json:"type"`
|
|
// Value 字段值,根据 Type 不同含义不同:
|
|
// - string: 固定字符串值
|
|
// - field: 要获取的字段路径
|
|
// - object: 忽略,使用 Fields
|
|
Value string `json:"value,omitempty"`
|
|
// Fields 当 Type 为 object 时,包含嵌套的字段定义
|
|
Fields Schema `json:"fields,omitempty"`
|
|
// Required 标识字段是否必须存在,仅对 FieldTypeField 类型有效
|
|
// 如果为 true,字段不存在时会返回错误
|
|
// 如果为 false(默认),字段不存在时返回 nil
|
|
Required bool `json:"required,omitempty"`
|
|
}
|
|
|
|
// Schema 定义字段映射表
|
|
type Schema map[string]Field
|
|
|
|
// Generate 根据 Schema 生成对象
|
|
// source: 源数据对象,用于 FieldTypeField 类型获取字段值
|
|
// 返回生成的 map[string]any 对象,如果 source 无效或处理失败则返回错误
|
|
func (s Schema) Generate(source any) (result map[string]any, err error) {
|
|
// 捕获 reflux.New 可能产生的 panic
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
result = nil
|
|
err = fmt.Errorf("failed to wrap source data: %v", r)
|
|
}
|
|
}()
|
|
|
|
var lkp valuex.Lookuper = valuex.Nil
|
|
|
|
if v, ok := source.(valuex.Lookuper); ok {
|
|
lkp = v
|
|
} else if source != nil {
|
|
// 使用 reflux 包装源数据,提供统一的访问接口
|
|
lkp = reflux.New(source)
|
|
}
|
|
|
|
result = make(map[string]any)
|
|
err = s.generateFields(lkp, result)
|
|
|
|
return result, err
|
|
}
|
|
|
|
// generateFields 递归生成字段
|
|
func (s Schema) generateFields(source valuex.Lookuper, target map[string]any) error {
|
|
for fieldName, field := range s {
|
|
value, err := field.generateValue(source)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate field %s: %w", fieldName, err)
|
|
}
|
|
target[fieldName] = value
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// generateValue 生成字段值
|
|
func (f Field) generateValue(source valuex.Lookuper) (any, error) {
|
|
switch f.Type {
|
|
case FieldTypeString:
|
|
return f.Value, nil
|
|
case FieldTypeField:
|
|
return getFieldValue(f.Value, source, f.Required)
|
|
case FieldTypeObject:
|
|
// 嵌套对象
|
|
nestedResult := make(map[string]any)
|
|
err := f.Fields.generateFields(source, nestedResult)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nestedResult, nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported field type: %s", f.Type)
|
|
}
|
|
}
|
|
|
|
// getFieldValue 从源对象获取字段值
|
|
// 支持点号分隔的路径,如 "user.name"
|
|
// required: 如果为 true,字段不存在时返回错误;如果为 false,字段不存在时返回 nil
|
|
func getFieldValue(path string, source valuex.Lookuper, required bool) (any, error) {
|
|
accessor, ok := source.Lookup(path)
|
|
|
|
// 检查字段是否存在
|
|
// 只有 required 为 true 时才返回错误
|
|
if !ok && required {
|
|
return nil, fmt.Errorf("field not found: %s", path)
|
|
}
|
|
|
|
return accessor.Any(), nil
|
|
}
|
|
|
|
// SchemaFromJSON 从 JSON 字符串创建 Schema
|
|
// 这是最简单直接的方式
|
|
func SchemaFromJSON(jsonStr string) (Schema, error) {
|
|
var schema Schema
|
|
err := json.Unmarshal([]byte(jsonStr), &schema)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
|
|
}
|
|
return schema, nil
|
|
}
|
|
|
|
// SchemaFromMap 从 map[string]any 创建 Schema
|
|
// 支持从 JSON 反序列化后的 map 转换
|
|
func SchemaFromMap(data map[string]any) (Schema, error) {
|
|
schema := make(Schema)
|
|
for key, value := range data {
|
|
field, err := fieldFromMap(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
schema[key] = field
|
|
}
|
|
return schema, nil
|
|
}
|
|
|
|
// fieldFromMap 从 map[string]any 创建 Field
|
|
func fieldFromMap(data any) (Field, error) {
|
|
m, ok := data.(map[string]any)
|
|
if !ok {
|
|
return Field{}, fmt.Errorf("invalid field data: expected map[string]any, got %T", data)
|
|
}
|
|
|
|
field := Field{}
|
|
|
|
// 解析 type
|
|
if typeVal, ok := m["type"]; ok {
|
|
if typeStr, ok := typeVal.(string); ok {
|
|
field.Type = FieldType(typeStr)
|
|
}
|
|
}
|
|
|
|
// 解析 value
|
|
if value, ok := m["value"]; ok {
|
|
if valueStr, ok := value.(string); ok {
|
|
field.Value = valueStr
|
|
}
|
|
}
|
|
|
|
// 解析 required
|
|
if required, ok := m["required"]; ok {
|
|
if requiredBool, ok := required.(bool); ok {
|
|
field.Required = requiredBool
|
|
}
|
|
}
|
|
|
|
// 解析 fields (嵌套)
|
|
if fields, ok := m["fields"]; ok {
|
|
if fieldsMap, ok := fields.(map[string]any); ok {
|
|
nestedSchema, err := SchemaFromMap(fieldsMap)
|
|
if err != nil {
|
|
return Field{}, err
|
|
}
|
|
field.Fields = nestedSchema
|
|
}
|
|
}
|
|
|
|
return field, nil
|
|
}
|