mirror of
https://github.com/oarkflow/mq.git
synced 2025-09-27 04:15:52 +08:00
267 lines
6.7 KiB
Go
267 lines
6.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/oarkflow/json"
|
|
"github.com/oarkflow/mq"
|
|
"github.com/oarkflow/mq/dag"
|
|
)
|
|
|
|
// FlattenHandler handles flattening array of objects to single objects
|
|
type FlattenHandler struct {
|
|
dag.Operation
|
|
}
|
|
|
|
func (h *FlattenHandler) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
|
data, err := dag.UnmarshalPayload[map[string]any](ctx, task.Payload)
|
|
if err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to unmarshal task payload: %w", err), Ctx: ctx}
|
|
}
|
|
|
|
operation, ok := h.Payload.Data["operation"].(string)
|
|
if !ok {
|
|
return mq.Result{Error: fmt.Errorf("operation not specified"), Ctx: ctx}
|
|
}
|
|
|
|
var result map[string]any
|
|
switch operation {
|
|
case "flatten_settings":
|
|
result = h.flattenSettings(data)
|
|
case "flatten_key_value":
|
|
result = h.flattenKeyValue(data)
|
|
case "flatten_nested_objects":
|
|
result = h.flattenNestedObjects(data)
|
|
case "flatten_array":
|
|
result = h.flattenArray(data)
|
|
default:
|
|
return mq.Result{Error: fmt.Errorf("unsupported operation: %s", operation), Ctx: ctx}
|
|
}
|
|
|
|
resultPayload, err := json.Marshal(result)
|
|
if err != nil {
|
|
return mq.Result{Error: fmt.Errorf("failed to marshal result: %w", err), Ctx: ctx}
|
|
}
|
|
|
|
return mq.Result{Payload: resultPayload, Ctx: ctx}
|
|
}
|
|
|
|
// flattenSettings converts array of settings objects with key, value, value_type to a flat object
|
|
func (h *FlattenHandler) flattenSettings(data map[string]any) map[string]any {
|
|
result := make(map[string]any)
|
|
sourceField := h.getSourceField()
|
|
|
|
// Copy all original data
|
|
for key, value := range data {
|
|
result[key] = value
|
|
}
|
|
|
|
if settingsArray, ok := data[sourceField].([]interface{}); ok {
|
|
flattened := make(map[string]any)
|
|
|
|
for _, item := range settingsArray {
|
|
if setting, ok := item.(map[string]interface{}); ok {
|
|
key, keyExists := setting["key"].(string)
|
|
value, valueExists := setting["value"]
|
|
valueType, typeExists := setting["value_type"].(string)
|
|
|
|
if keyExists && valueExists {
|
|
// Convert value based on value_type
|
|
if typeExists {
|
|
flattened[key] = h.convertValue(value, valueType)
|
|
} else {
|
|
flattened[key] = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
targetField := h.getTargetField()
|
|
result[targetField] = flattened
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// flattenKeyValue converts array of key-value objects to a flat object
|
|
func (h *FlattenHandler) flattenKeyValue(data map[string]any) map[string]any {
|
|
result := make(map[string]any)
|
|
sourceField := h.getSourceField()
|
|
keyField := h.getKeyField()
|
|
valueField := h.getValueField()
|
|
|
|
// Copy all original data
|
|
for key, value := range data {
|
|
result[key] = value
|
|
}
|
|
|
|
if kvArray, ok := data[sourceField].([]interface{}); ok {
|
|
flattened := make(map[string]any)
|
|
|
|
for _, item := range kvArray {
|
|
if kvPair, ok := item.(map[string]interface{}); ok {
|
|
if key, keyExists := kvPair[keyField]; keyExists {
|
|
if value, valueExists := kvPair[valueField]; valueExists {
|
|
if keyStr, ok := key.(string); ok {
|
|
flattened[keyStr] = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
targetField := h.getTargetField()
|
|
result[targetField] = flattened
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// flattenNestedObjects flattens nested objects using dot notation
|
|
func (h *FlattenHandler) flattenNestedObjects(data map[string]any) map[string]any {
|
|
result := make(map[string]any)
|
|
separator := h.getSeparator()
|
|
|
|
h.flattenRecursive(data, "", result, separator)
|
|
return result
|
|
}
|
|
|
|
// flattenArray flattens arrays by creating numbered fields
|
|
func (h *FlattenHandler) flattenArray(data map[string]any) map[string]any {
|
|
result := make(map[string]any)
|
|
sourceField := h.getSourceField()
|
|
|
|
// Copy all original data except the source field
|
|
for key, value := range data {
|
|
if key != sourceField {
|
|
result[key] = value
|
|
}
|
|
}
|
|
|
|
if array, ok := data[sourceField].([]interface{}); ok {
|
|
for i, item := range array {
|
|
if obj, ok := item.(map[string]interface{}); ok {
|
|
for key, value := range obj {
|
|
result[fmt.Sprintf("%s_%d_%s", sourceField, i, key)] = value
|
|
}
|
|
} else {
|
|
result[fmt.Sprintf("%s_%d", sourceField, i)] = item
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (h *FlattenHandler) flattenRecursive(obj map[string]any, prefix string, result map[string]any, separator string) {
|
|
for key, value := range obj {
|
|
newKey := key
|
|
if prefix != "" {
|
|
newKey = prefix + separator + key
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case map[string]interface{}:
|
|
nestedMap := make(map[string]any)
|
|
for k, val := range v {
|
|
nestedMap[k] = val
|
|
}
|
|
h.flattenRecursive(nestedMap, newKey, result, separator)
|
|
case []interface{}:
|
|
// For arrays, create numbered fields
|
|
for i, item := range v {
|
|
itemKey := fmt.Sprintf("%s%s%d", newKey, separator, i)
|
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
|
nestedMap := make(map[string]any)
|
|
for k, val := range itemMap {
|
|
nestedMap[k] = val
|
|
}
|
|
h.flattenRecursive(nestedMap, itemKey, result, separator)
|
|
} else {
|
|
result[itemKey] = item
|
|
}
|
|
}
|
|
default:
|
|
result[newKey] = value
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *FlattenHandler) convertValue(value interface{}, valueType string) interface{} {
|
|
switch valueType {
|
|
case "string":
|
|
return fmt.Sprintf("%v", value)
|
|
case "int", "integer":
|
|
if str, ok := value.(string); ok {
|
|
var intVal int
|
|
fmt.Sscanf(str, "%d", &intVal)
|
|
return intVal
|
|
}
|
|
return value
|
|
case "float", "number":
|
|
if str, ok := value.(string); ok {
|
|
var floatVal float64
|
|
fmt.Sscanf(str, "%f", &floatVal)
|
|
return floatVal
|
|
}
|
|
return value
|
|
case "bool", "boolean":
|
|
if str, ok := value.(string); ok {
|
|
return str == "true" || str == "1"
|
|
}
|
|
return value
|
|
case "json":
|
|
if str, ok := value.(string); ok {
|
|
var jsonVal interface{}
|
|
if err := json.Unmarshal([]byte(str), &jsonVal); err == nil {
|
|
return jsonVal
|
|
}
|
|
}
|
|
return value
|
|
default:
|
|
return value
|
|
}
|
|
}
|
|
|
|
func (h *FlattenHandler) getSourceField() string {
|
|
if field, ok := h.Payload.Data["source_field"].(string); ok {
|
|
return field
|
|
}
|
|
return "settings" // Default
|
|
}
|
|
|
|
func (h *FlattenHandler) getTargetField() string {
|
|
if field, ok := h.Payload.Data["target_field"].(string); ok {
|
|
return field
|
|
}
|
|
return "flattened" // Default
|
|
}
|
|
|
|
func (h *FlattenHandler) getKeyField() string {
|
|
if field, ok := h.Payload.Data["key_field"].(string); ok {
|
|
return field
|
|
}
|
|
return "key" // Default
|
|
}
|
|
|
|
func (h *FlattenHandler) getValueField() string {
|
|
if field, ok := h.Payload.Data["value_field"].(string); ok {
|
|
return field
|
|
}
|
|
return "value" // Default
|
|
}
|
|
|
|
func (h *FlattenHandler) getSeparator() string {
|
|
if sep, ok := h.Payload.Data["separator"].(string); ok {
|
|
return sep
|
|
}
|
|
return "." // Default separator for flattening
|
|
}
|
|
|
|
func NewFlattenHandler(id string) *FlattenHandler {
|
|
return &FlattenHandler{
|
|
Operation: dag.Operation{ID: id, Key: "flatten", Type: dag.Function, Tags: []string{"data", "transformation", "flatten"}},
|
|
}
|
|
}
|