mirror of
https://gitee.com/rulego/streamsql.git
synced 2026-03-15 06:47:26 +00:00
621 lines
15 KiB
Go
621 lines
15 KiB
Go
package fieldpath
|
||
|
||
import (
|
||
"fmt"
|
||
"reflect"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// FieldAccessor 字段访问器结构,用于解析复杂的字段路径
|
||
type FieldAccessor struct {
|
||
Parts []FieldPart
|
||
}
|
||
|
||
// FieldPart 字段路径的单个部分
|
||
type FieldPart struct {
|
||
Type string // "field", "array_index", "map_key"
|
||
Name string // 字段名或键名
|
||
Index int // 数组索引(当Type为"array_index"时)
|
||
Key string // Map键(当Type为"map_key"时)
|
||
KeyType string // 键类型:"string", "number"
|
||
}
|
||
|
||
// 正则表达式用于解析复杂字段路径
|
||
var (
|
||
// 匹配数组索引:[0], [1], [-1] 等
|
||
arrayIndexRegex = regexp.MustCompile(`\[(-?\d+)\]`)
|
||
// 匹配字符串键:["key"], ['key'] 等
|
||
stringKeyRegex = regexp.MustCompile(`\[['"]([^'"]*)['"]\]`)
|
||
// 匹配数字键:[123] 等(与数组索引相同,但在Map上下文中)
|
||
numberKeyRegex = regexp.MustCompile(`\[(\d+)\]`)
|
||
)
|
||
|
||
// ParseFieldPath 解析字段路径,支持点号、数组索引、Map键等复杂访问
|
||
// 支持的格式:
|
||
// - a.b.c (嵌套字段)
|
||
// - a.b[0] (数组索引)
|
||
// - a.b[0].c (数组元素的字段)
|
||
// - a.b["key"] (字符串键)
|
||
// - a.b['key'] (字符串键)
|
||
// - a.b[123] (数字键或数组索引)
|
||
// - a[0].b[1].c["key"] (混合访问)
|
||
func ParseFieldPath(fieldPath string) (*FieldAccessor, error) {
|
||
if fieldPath == "" {
|
||
return nil, nil
|
||
}
|
||
|
||
accessor := &FieldAccessor{
|
||
Parts: make([]FieldPart, 0),
|
||
}
|
||
|
||
// 首先处理点号分割的基本路径
|
||
parts := strings.Split(fieldPath, ".")
|
||
|
||
for _, part := range parts {
|
||
if part == "" {
|
||
continue
|
||
}
|
||
|
||
// 检查当前部分是否包含数组索引或Map键访问
|
||
if strings.Contains(part, "[") {
|
||
// 处理包含索引/键的复杂部分
|
||
err := parseComplexPart(part, accessor)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
} else {
|
||
// 简单字段名
|
||
accessor.Parts = append(accessor.Parts, FieldPart{
|
||
Type: "field",
|
||
Name: part,
|
||
})
|
||
}
|
||
}
|
||
|
||
return accessor, nil
|
||
}
|
||
|
||
// parseComplexPart 解析包含索引或键访问的复杂部分
|
||
func parseComplexPart(part string, accessor *FieldAccessor) error {
|
||
// 找到第一个 '[' 的位置
|
||
bracketIndex := strings.Index(part, "[")
|
||
if bracketIndex == -1 {
|
||
// 没有括号,当作普通字段处理
|
||
accessor.Parts = append(accessor.Parts, FieldPart{
|
||
Type: "field",
|
||
Name: part,
|
||
})
|
||
return nil
|
||
}
|
||
|
||
// 如果有字段名部分,先添加字段访问
|
||
if bracketIndex > 0 {
|
||
fieldName := part[:bracketIndex]
|
||
accessor.Parts = append(accessor.Parts, FieldPart{
|
||
Type: "field",
|
||
Name: fieldName,
|
||
})
|
||
}
|
||
|
||
// 解析剩余的索引/键访问部分
|
||
remaining := part[bracketIndex:]
|
||
|
||
// 依次处理所有的 [xxx] 部分
|
||
for len(remaining) > 0 && strings.HasPrefix(remaining, "[") {
|
||
// 找到匹配的右括号
|
||
rightBracket := strings.Index(remaining, "]")
|
||
if rightBracket == -1 {
|
||
return &FieldAccessError{
|
||
Path: part,
|
||
Message: "unmatched bracket in field path",
|
||
}
|
||
}
|
||
|
||
// 提取括号内的内容
|
||
bracketContent := remaining[1:rightBracket]
|
||
|
||
// 解析括号内容
|
||
fieldPart, err := parseBracketContent(bracketContent)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
accessor.Parts = append(accessor.Parts, fieldPart)
|
||
|
||
// 移动到下一部分
|
||
remaining = remaining[rightBracket+1:]
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// parseBracketContent 解析括号内的内容
|
||
func parseBracketContent(content string) (FieldPart, error) {
|
||
content = strings.TrimSpace(content)
|
||
|
||
// 检查是否是字符串键(带引号)
|
||
if (strings.HasPrefix(content, "'") && strings.HasSuffix(content, "'")) ||
|
||
(strings.HasPrefix(content, "\"") && strings.HasSuffix(content, "\"")) {
|
||
// 字符串键
|
||
key := content[1 : len(content)-1] // 去掉引号
|
||
return FieldPart{
|
||
Type: "map_key",
|
||
Key: key,
|
||
KeyType: "string",
|
||
}, nil
|
||
}
|
||
|
||
// 检查是否是数字
|
||
if num, err := strconv.Atoi(content); err == nil {
|
||
// 数字,可能是数组索引或数字键
|
||
return FieldPart{
|
||
Type: "array_index", // 默认当作数组索引,实际使用时会根据数据类型调整
|
||
Index: num,
|
||
Key: content,
|
||
KeyType: "number",
|
||
}, nil
|
||
}
|
||
|
||
return FieldPart{}, &FieldAccessError{
|
||
Path: content,
|
||
Message: "invalid bracket content, expected number or quoted string",
|
||
}
|
||
}
|
||
|
||
// GetNestedField 从嵌套的map或结构体中获取字段值
|
||
// 支持点号分隔的字段路径、数组索引、Map键等复杂操作
|
||
// 支持的格式:
|
||
// - "device.info.name" (嵌套字段)
|
||
// - "data[0]" (数组索引)
|
||
// - "users[0].name" (数组元素的字段)
|
||
// - "config['key']" (字符串键)
|
||
// - "items[0][1]" (多维数组)
|
||
// - "nested.data[0].field['key']" (混合访问)
|
||
func GetNestedField(data interface{}, fieldPath string) (interface{}, bool) {
|
||
if fieldPath == "" {
|
||
return nil, false
|
||
}
|
||
|
||
// 解析字段路径
|
||
accessor, err := ParseFieldPath(fieldPath)
|
||
if err != nil {
|
||
// 如果解析失败,回退到原有的简单点号访问
|
||
return getNestedFieldSimple(data, fieldPath)
|
||
}
|
||
|
||
if accessor == nil || len(accessor.Parts) == 0 {
|
||
return nil, false
|
||
}
|
||
|
||
// 按照解析的路径逐步访问
|
||
current := data
|
||
for _, part := range accessor.Parts {
|
||
val, found := accessFieldPart(current, part)
|
||
if !found {
|
||
return nil, false
|
||
}
|
||
current = val
|
||
}
|
||
|
||
return current, true
|
||
}
|
||
|
||
// accessFieldPart 访问单个字段部分
|
||
func accessFieldPart(data interface{}, part FieldPart) (interface{}, bool) {
|
||
if data == nil {
|
||
return nil, false
|
||
}
|
||
|
||
v := reflect.ValueOf(data)
|
||
|
||
// 如果是指针,解引用
|
||
if v.Kind() == reflect.Ptr {
|
||
if v.IsNil() {
|
||
return nil, false
|
||
}
|
||
v = v.Elem()
|
||
}
|
||
|
||
switch part.Type {
|
||
case "field":
|
||
return getFieldValue(data, part.Name)
|
||
|
||
case "array_index":
|
||
return getArrayElement(v, part.Index)
|
||
|
||
case "map_key":
|
||
return getMapValue(v, part.Key, part.KeyType)
|
||
|
||
default:
|
||
return nil, false
|
||
}
|
||
}
|
||
|
||
// getArrayElement 获取数组或切片元素
|
||
func getArrayElement(v reflect.Value, index int) (interface{}, bool) {
|
||
switch v.Kind() {
|
||
case reflect.Slice, reflect.Array:
|
||
length := v.Len()
|
||
|
||
// 支持负数索引(从末尾开始)
|
||
if index < 0 {
|
||
index = length + index
|
||
}
|
||
|
||
if index < 0 || index >= length {
|
||
return nil, false
|
||
}
|
||
|
||
elem := v.Index(index)
|
||
return elem.Interface(), true
|
||
|
||
case reflect.Map:
|
||
// 如果数据是Map,将索引作为键来访问
|
||
key := reflect.ValueOf(index)
|
||
mapVal := v.MapIndex(key)
|
||
if mapVal.IsValid() {
|
||
return mapVal.Interface(), true
|
||
}
|
||
|
||
// 尝试字符串形式的索引
|
||
strKey := reflect.ValueOf(strconv.Itoa(index))
|
||
mapVal = v.MapIndex(strKey)
|
||
if mapVal.IsValid() {
|
||
return mapVal.Interface(), true
|
||
}
|
||
|
||
return nil, false
|
||
|
||
default:
|
||
return nil, false
|
||
}
|
||
}
|
||
|
||
// getMapValue 获取Map值
|
||
func getMapValue(v reflect.Value, key, keyType string) (interface{}, bool) {
|
||
if v.Kind() != reflect.Map {
|
||
return nil, false
|
||
}
|
||
|
||
// 首先尝试字符串键
|
||
if keyType == "string" || v.Type().Key().Kind() == reflect.String {
|
||
mapVal := v.MapIndex(reflect.ValueOf(key))
|
||
if mapVal.IsValid() {
|
||
return mapVal.Interface(), true
|
||
}
|
||
}
|
||
|
||
// 如果是数字类型的键
|
||
if keyType == "number" {
|
||
if num, err := strconv.Atoi(key); err == nil {
|
||
// 尝试int键
|
||
mapVal := v.MapIndex(reflect.ValueOf(num))
|
||
if mapVal.IsValid() {
|
||
return mapVal.Interface(), true
|
||
}
|
||
|
||
// 尝试字符串形式的数字键
|
||
mapVal = v.MapIndex(reflect.ValueOf(key))
|
||
if mapVal.IsValid() {
|
||
return mapVal.Interface(), true
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil, false
|
||
}
|
||
|
||
// getNestedFieldSimple 原有的简单点号访问(向后兼容)
|
||
func getNestedFieldSimple(data interface{}, fieldPath string) (interface{}, bool) {
|
||
if fieldPath == "" {
|
||
return nil, false
|
||
}
|
||
|
||
// 分割字段路径
|
||
fields := strings.Split(fieldPath, ".")
|
||
current := data
|
||
|
||
for _, field := range fields {
|
||
val, found := getFieldValue(current, field)
|
||
if !found {
|
||
return nil, false
|
||
}
|
||
current = val
|
||
}
|
||
|
||
return current, true
|
||
}
|
||
|
||
// getFieldValue 从单个层级获取字段值
|
||
func getFieldValue(data interface{}, fieldName string) (interface{}, bool) {
|
||
if data == nil {
|
||
return nil, false
|
||
}
|
||
|
||
v := reflect.ValueOf(data)
|
||
|
||
// 如果是指针,解引用
|
||
if v.Kind() == reflect.Ptr {
|
||
if v.IsNil() {
|
||
return nil, false
|
||
}
|
||
v = v.Elem()
|
||
}
|
||
|
||
switch v.Kind() {
|
||
case reflect.Map:
|
||
// 处理 map[string]interface{}
|
||
if v.Type().Key().Kind() == reflect.String {
|
||
mapVal := v.MapIndex(reflect.ValueOf(fieldName))
|
||
if mapVal.IsValid() {
|
||
return mapVal.Interface(), true
|
||
}
|
||
}
|
||
return nil, false
|
||
|
||
case reflect.Struct:
|
||
// 处理结构体
|
||
fieldVal := v.FieldByName(fieldName)
|
||
if fieldVal.IsValid() {
|
||
return fieldVal.Interface(), true
|
||
}
|
||
return nil, false
|
||
|
||
default:
|
||
return nil, false
|
||
}
|
||
}
|
||
|
||
// SetNestedField 在嵌套的map中设置字段值,支持复杂路径
|
||
// 如果路径中的某些层级不存在,会自动创建
|
||
func SetNestedField(data map[string]interface{}, fieldPath string, value interface{}) error {
|
||
if fieldPath == "" {
|
||
return &FieldAccessError{
|
||
Path: fieldPath,
|
||
Message: "empty field path",
|
||
}
|
||
}
|
||
|
||
// 解析字段路径
|
||
accessor, err := ParseFieldPath(fieldPath)
|
||
if err != nil {
|
||
// 如果解析失败,回退到原有的简单设置
|
||
setNestedFieldSimple(data, fieldPath, value)
|
||
return nil
|
||
}
|
||
|
||
if accessor == nil || len(accessor.Parts) == 0 {
|
||
return &FieldAccessError{
|
||
Path: fieldPath,
|
||
Message: "invalid field path",
|
||
}
|
||
}
|
||
|
||
// 逐级创建路径并设置值
|
||
current := data
|
||
|
||
// 处理除最后一个部分外的所有部分
|
||
for i := 0; i < len(accessor.Parts)-1; i++ {
|
||
part := accessor.Parts[i]
|
||
|
||
if part.Type != "field" {
|
||
return &FieldAccessError{
|
||
Path: fieldPath,
|
||
Message: "complex path setting only supports field access in intermediate parts",
|
||
}
|
||
}
|
||
|
||
// 确保中间路径存在
|
||
if next, exists := current[part.Name]; exists {
|
||
if nextMap, ok := next.(map[string]interface{}); ok {
|
||
current = nextMap
|
||
} else {
|
||
// 如果存在但不是map,创建新的map覆盖
|
||
newMap := make(map[string]interface{})
|
||
current[part.Name] = newMap
|
||
current = newMap
|
||
}
|
||
} else {
|
||
// 如果不存在,创建新的map
|
||
newMap := make(map[string]interface{})
|
||
current[part.Name] = newMap
|
||
current = newMap
|
||
}
|
||
}
|
||
|
||
// 处理最后一个部分
|
||
lastPart := accessor.Parts[len(accessor.Parts)-1]
|
||
if lastPart.Type == "field" {
|
||
current[lastPart.Name] = value
|
||
} else {
|
||
return &FieldAccessError{
|
||
Path: fieldPath,
|
||
Message: "complex path setting only supports field access for final part",
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// setNestedFieldSimple 原有的简单设置(向后兼容)
|
||
func setNestedFieldSimple(data map[string]interface{}, fieldPath string, value interface{}) {
|
||
if fieldPath == "" {
|
||
return
|
||
}
|
||
|
||
fields := strings.Split(fieldPath, ".")
|
||
current := data
|
||
|
||
// 遍历到倒数第二层,确保路径存在
|
||
for i := 0; i < len(fields)-1; i++ {
|
||
field := fields[i]
|
||
if next, exists := current[field]; exists {
|
||
if nextMap, ok := next.(map[string]interface{}); ok {
|
||
current = nextMap
|
||
} else {
|
||
// 如果存在但不是map,创建新的map覆盖
|
||
newMap := make(map[string]interface{})
|
||
current[field] = newMap
|
||
current = newMap
|
||
}
|
||
} else {
|
||
// 如果不存在,创建新的map
|
||
newMap := make(map[string]interface{})
|
||
current[field] = newMap
|
||
current = newMap
|
||
}
|
||
}
|
||
|
||
// 设置最终的值
|
||
lastField := fields[len(fields)-1]
|
||
current[lastField] = value
|
||
}
|
||
|
||
// IsNestedField 检查字段名是否包含点号或数组索引(嵌套字段)
|
||
func IsNestedField(fieldName string) bool {
|
||
return strings.Contains(fieldName, ".") || strings.Contains(fieldName, "[")
|
||
}
|
||
|
||
// ExtractTopLevelField 从嵌套字段路径中提取顶级字段名
|
||
// 例如:"device.info.name" 返回 "device"
|
||
//
|
||
// "data[0].name" 返回 "data"
|
||
// "a.b[0].c['key']" 返回 "a"
|
||
func ExtractTopLevelField(fieldPath string) string {
|
||
if fieldPath == "" {
|
||
return ""
|
||
}
|
||
|
||
// 找到第一个分隔符(点号或左括号)
|
||
dotIndex := strings.Index(fieldPath, ".")
|
||
bracketIndex := strings.Index(fieldPath, "[")
|
||
|
||
// 取较早出现的分隔符位置
|
||
firstSeparator := -1
|
||
if dotIndex >= 0 && bracketIndex >= 0 {
|
||
if dotIndex < bracketIndex {
|
||
firstSeparator = dotIndex
|
||
} else {
|
||
firstSeparator = bracketIndex
|
||
}
|
||
} else if dotIndex >= 0 {
|
||
firstSeparator = dotIndex
|
||
} else if bracketIndex >= 0 {
|
||
firstSeparator = bracketIndex
|
||
}
|
||
|
||
// 如果找到分隔符,返回分隔符之前的部分
|
||
if firstSeparator > 0 {
|
||
return fieldPath[:firstSeparator]
|
||
}
|
||
|
||
// 没有分隔符,整个路径就是顶级字段
|
||
return fieldPath
|
||
}
|
||
|
||
// GetAllReferencedFields 获取嵌套字段路径中引用的所有顶级字段
|
||
// 例如:["device.info.name", "sensor.temperature", "data[0].value"] 返回 ["device", "sensor", "data"]
|
||
func GetAllReferencedFields(fieldPaths []string) []string {
|
||
topLevelFields := make(map[string]bool)
|
||
|
||
for _, path := range fieldPaths {
|
||
if path != "" {
|
||
topField := ExtractTopLevelField(path)
|
||
if topField != "" {
|
||
topLevelFields[topField] = true
|
||
}
|
||
}
|
||
}
|
||
|
||
result := make([]string, 0, len(topLevelFields))
|
||
for field := range topLevelFields {
|
||
result = append(result, field)
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// ValidateFieldPath 验证字段路径的格式是否正确
|
||
func ValidateFieldPath(fieldPath string) error {
|
||
if fieldPath == "" {
|
||
return &FieldAccessError{
|
||
Path: fieldPath,
|
||
Message: "empty field path",
|
||
}
|
||
}
|
||
|
||
_, err := ParseFieldPath(fieldPath)
|
||
return err
|
||
}
|
||
|
||
// FieldAccessError 字段访问错误
|
||
type FieldAccessError struct {
|
||
Path string
|
||
Message string
|
||
}
|
||
|
||
func (e *FieldAccessError) Error() string {
|
||
return fmt.Sprintf("field access error for path '%s': %s", e.Path, e.Message)
|
||
}
|
||
|
||
// GetFieldPathDepth 获取字段路径的深度
|
||
func GetFieldPathDepth(fieldPath string) int {
|
||
if fieldPath == "" {
|
||
return 0
|
||
}
|
||
|
||
accessor, err := ParseFieldPath(fieldPath)
|
||
if err != nil {
|
||
// 回退到简单计算
|
||
return len(strings.Split(fieldPath, "."))
|
||
}
|
||
|
||
if accessor == nil {
|
||
return 0
|
||
}
|
||
|
||
return len(accessor.Parts)
|
||
}
|
||
|
||
// NormalizeFieldPath 规范化字段路径格式
|
||
func NormalizeFieldPath(fieldPath string) string {
|
||
accessor, err := ParseFieldPath(fieldPath)
|
||
if err != nil {
|
||
return fieldPath // 如果解析失败,返回原路径
|
||
}
|
||
|
||
if accessor == nil || len(accessor.Parts) == 0 {
|
||
return fieldPath
|
||
}
|
||
|
||
var result strings.Builder
|
||
for i, part := range accessor.Parts {
|
||
if i > 0 && part.Type == "field" {
|
||
result.WriteString(".")
|
||
}
|
||
|
||
switch part.Type {
|
||
case "field":
|
||
result.WriteString(part.Name)
|
||
case "array_index":
|
||
result.WriteString("[")
|
||
result.WriteString(strconv.Itoa(part.Index))
|
||
result.WriteString("]")
|
||
case "map_key":
|
||
result.WriteString("[")
|
||
if part.KeyType == "string" {
|
||
result.WriteString("'")
|
||
result.WriteString(part.Key)
|
||
result.WriteString("'")
|
||
} else {
|
||
result.WriteString(part.Key)
|
||
}
|
||
result.WriteString("]")
|
||
}
|
||
}
|
||
|
||
return result.String()
|
||
}
|