feat: 添加 Lookuper 接口和 MustLookup 方法

- 新增 Lookuper 接口,提供 Lookup 和 MustLookup 两种路径查找方式
- Accessor 接口继承 Lookuper,保持向后兼容
- MustLookup 在路径不存在时返回 Nil 访问器,简化调用代码
- 更新 fieldx.Schema 使用 Lookuper 接口,支持更灵活的数据源
- 添加 Required 字段选项,控制字段不存在时的行为
This commit is contained in:
2026-01-05 16:59:31 +08:00
parent b73099d205
commit 39da1d55dd
4 changed files with 55 additions and 15 deletions

View File

@@ -3,9 +3,9 @@ package fieldx
import (
"encoding/json"
"fmt"
"strings"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/reflux/valuex"
)
// FieldType 定义字段类型
@@ -31,6 +31,10 @@ type Field struct {
Value string `json:"value,omitempty"`
// Fields 当 Type 为 object 时,包含嵌套的字段定义
Fields Schema `json:"fields,omitempty"`
// Required 标识字段是否必须存在,仅对 FieldTypeField 类型有效
// 如果为 true,字段不存在时会返回错误
// 如果为 false(默认),字段不存在时返回 nil
Required bool `json:"required,omitempty"`
}
// Schema 定义字段映射表
@@ -48,17 +52,23 @@ func (s Schema) Generate(source any) (result map[string]any, err error) {
}
}()
// 使用 reflux 包装源数据,提供统一的访问接口
rfx := reflux.New(source)
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(rfx, result)
err = s.generateFields(lkp, result)
return result, err
}
// generateFields 递归生成字段
func (s Schema) generateFields(source reflux.R, target map[string]any) error {
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 {
@@ -70,12 +80,12 @@ func (s Schema) generateFields(source reflux.R, target map[string]any) error {
}
// generateValue 生成字段值
func (f Field) generateValue(source reflux.R) (any, error) {
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)
return getFieldValue(f.Value, source, f.Required)
case FieldTypeObject:
// 嵌套对象
nestedResult := make(map[string]any)
@@ -91,17 +101,16 @@ func (f Field) generateValue(source reflux.R) (any, error) {
// getFieldValue 从源对象获取字段值
// 支持点号分隔的路径,如 "user.name"
func getFieldValue(path string, source reflux.R) (any, error) {
// 分割路径
parts := strings.Split(path, ".")
// required: 如果为 true,字段不存在时返回错误;如果为 false,字段不存在时返回 nil
func getFieldValue(path string, source valuex.Lookuper, required bool) (any, error) {
accessor, ok := source.Lookup(path)
// 检查字段是否存在
if !source.Exists(parts...) {
// 只有 required 为 true 时才返回错误
if !ok && required {
return nil, fmt.Errorf("field not found: %s", path)
}
// 获取值
accessor := source.Get(parts...)
return accessor.Any(), nil
}
@@ -153,6 +162,13 @@ func fieldFromMap(data any) (Field, error) {
}
}
// 解析 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 {