mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-04 18:45:38 +08:00
### Summary Port the following PRs to GO in this PR https://github.com/infiniflow/ragflow/pull/14210 https://github.com/infiniflow/ragflow/pull/14641 https://github.com/infiniflow/ragflow/pull/15220 https://github.com/infiniflow/ragflow/pull/15228 https://github.com/infiniflow/ragflow/pull/15384 https://github.com/infiniflow/ragflow/pull/15754 https://github.com/infiniflow/ragflow/pull/16413 https://github.com/infiniflow/ragflow/pull/16483 https://github.com/infiniflow/ragflow/pull/16419 https://github.com/infiniflow/ragflow/pull/16361 https://github.com/infiniflow/ragflow/pull/16050
153 lines
4.2 KiB
Go
153 lines
4.2 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
// json_safe.go — Safe JSON marshaling with fallback for non-serializable
|
|
// values, mirroring the Python PR #14210 approach.
|
|
//
|
|
// Agent components may store functools.partial-like objects (function
|
|
// closures) or non-copyable client objects in their output slots. These
|
|
// values leak into canvas state maps and must not crash json.Marshal.
|
|
// This file provides helpers that recursively clean state maps before
|
|
// serialization, converting function values and channels to nil.
|
|
package runtime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"ragflow/internal/common"
|
|
)
|
|
|
|
// SafeJSONMarshal marshals v to JSON after recursively replacing
|
|
// non-serializable Go values (func, chan) with nil. This mirrors the
|
|
// Python PR #14210 _serialize_default / _canvas_json_default fallback
|
|
// that converts callables to None.
|
|
//
|
|
// Use this in preference to json.Marshal when the input is a canvas
|
|
// state map (map[string]any) that may contain function closures,
|
|
// channels, or other types encoding/json cannot handle.
|
|
func SafeJSONMarshal(v any) ([]byte, error) {
|
|
cleaned := cleanForJSON(v)
|
|
return json.Marshal(cleaned)
|
|
}
|
|
|
|
// cleanForJSON recursively replaces non-JSON-serializable values with nil.
|
|
func cleanForJSON(v any) any {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
switch val := v.(type) {
|
|
case map[string]any:
|
|
return cleanMap(val)
|
|
case map[string]map[string]any:
|
|
out := make(map[string]map[string]any, len(val))
|
|
for k, vv := range val {
|
|
out[k] = cleanMap(vv)
|
|
}
|
|
return out
|
|
case []map[string]any:
|
|
out := make([]any, len(val))
|
|
for i, vv := range val {
|
|
out[i] = cleanMap(vv)
|
|
}
|
|
return out
|
|
case []any:
|
|
out := make([]any, len(val))
|
|
for i, vv := range val {
|
|
out[i] = cleanForJSON(vv)
|
|
}
|
|
return out
|
|
default:
|
|
rv := reflect.ValueOf(v)
|
|
if !rv.IsValid() {
|
|
return nil
|
|
}
|
|
switch rv.Kind() {
|
|
case reflect.Func, reflect.Chan:
|
|
common.Info("cleanForJSON: converting non-serializable value to nil",
|
|
zap.String("type", rv.Type().String()))
|
|
return nil
|
|
case reflect.Map:
|
|
// Generic map fallback (e.g., map[string]int, etc.).
|
|
// Build as map[string]any because cleaned values may
|
|
// change type (func→nil, typed slice→[]any, etc.) and
|
|
// SetMapIndex on an original-typed map would panic.
|
|
cleaned := make(map[string]any, rv.Len())
|
|
for iter := rv.MapRange(); iter.Next(); {
|
|
key := fmt.Sprint(iter.Key().Interface())
|
|
cleaned[key] = cleanForJSON(iter.Value().Interface())
|
|
}
|
|
return cleaned
|
|
case reflect.Slice, reflect.Array:
|
|
length := rv.Len()
|
|
cleaned := make([]any, length)
|
|
for i := 0; i < length; i++ {
|
|
cleaned[i] = cleanForJSON(rv.Index(i).Interface())
|
|
}
|
|
return cleaned
|
|
case reflect.Ptr:
|
|
if rv.IsNil() {
|
|
return nil
|
|
}
|
|
return cleanForJSON(rv.Elem().Interface())
|
|
case reflect.Interface:
|
|
if rv.IsNil() {
|
|
return nil
|
|
}
|
|
return cleanForJSON(rv.Elem().Interface())
|
|
case reflect.Struct:
|
|
cleaned := make(map[string]any, rv.NumField())
|
|
t := rv.Type()
|
|
for i := 0; i < rv.NumField(); i++ {
|
|
field := t.Field(i)
|
|
if !field.IsExported() {
|
|
continue
|
|
}
|
|
fv := rv.Field(i)
|
|
if !fv.CanInterface() {
|
|
continue
|
|
}
|
|
key := field.Name
|
|
if tag := field.Tag.Get("json"); tag != "" {
|
|
name, _, _ := strings.Cut(tag, ",")
|
|
if name == "-" {
|
|
continue
|
|
}
|
|
if name != "" {
|
|
key = name
|
|
}
|
|
}
|
|
cleaned[key] = cleanForJSON(fv.Interface())
|
|
}
|
|
return cleaned
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
}
|
|
|
|
func cleanMap(m map[string]any) map[string]any {
|
|
out := make(map[string]any, len(m))
|
|
for k, v := range m {
|
|
out[k] = cleanForJSON(v)
|
|
}
|
|
return out
|
|
}
|