Files
mq/handlers/flatten_handler.go
2025-09-18 15:53:25 +05:45

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"}},
}
}