generated from dellevin/template
278 lines
5.9 KiB
Go
278 lines
5.9 KiB
Go
package flatted
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// flattedIndex is a internal type used to distinguish between
|
|
// actual strings and flatted indices during the reconstruction phase.
|
|
type flattedIndex string
|
|
|
|
// Stringify converts a Go value into a specialized flatted JSON string.
|
|
func Stringify(value any, replacer any, space any) (string, error) {
|
|
knownKeys := []any{}
|
|
knownValues := []string{}
|
|
input := []any{}
|
|
|
|
index := func(v any) string {
|
|
input = append(input, v)
|
|
idx := strconv.Itoa(len(input) - 1)
|
|
knownKeys = append(knownKeys, v)
|
|
knownValues = append(knownValues, idx)
|
|
return idx
|
|
}
|
|
|
|
relate := func(v any) any {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
rv := reflect.ValueOf(v)
|
|
kind := rv.Kind()
|
|
if kind == reflect.String || kind == reflect.Slice || kind == reflect.Map || kind == reflect.Ptr {
|
|
for i, k := range knownKeys {
|
|
if kind == reflect.String {
|
|
if k == v {
|
|
return knownValues[i]
|
|
}
|
|
} else {
|
|
rk := reflect.ValueOf(k)
|
|
if rk.Kind() == kind && rk.Pointer() == rv.Pointer() {
|
|
return knownValues[i]
|
|
}
|
|
}
|
|
}
|
|
return index(v)
|
|
}
|
|
return v
|
|
}
|
|
|
|
transform := func(v any) any {
|
|
rv := reflect.ValueOf(v)
|
|
if !rv.IsValid() {
|
|
return nil
|
|
}
|
|
if _, ok := v.(json.Marshaler); ok {
|
|
return v
|
|
}
|
|
// Dereference pointers to process the underlying Slice, Map, or Array
|
|
for rv.Kind() == reflect.Ptr && !rv.IsNil() {
|
|
rv = rv.Elem()
|
|
}
|
|
switch rv.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
res := make([]any, rv.Len())
|
|
for i := 0; i < rv.Len(); i++ {
|
|
res[i] = relate(rv.Index(i).Interface())
|
|
}
|
|
return res
|
|
case reflect.Map:
|
|
res := make(map[string]any)
|
|
keys := rv.MapKeys()
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
return keys[i].String() < keys[j].String()
|
|
})
|
|
|
|
whitelist, isWhitelist := replacer.([]string)
|
|
for _, key := range keys {
|
|
kStr := key.String()
|
|
if isWhitelist {
|
|
found := false
|
|
for _, w := range whitelist {
|
|
if w == kStr {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
continue
|
|
}
|
|
}
|
|
res[kStr] = relate(rv.MapIndex(key).Interface())
|
|
}
|
|
return res
|
|
case reflect.Struct:
|
|
res := make(map[string]any)
|
|
t := rv.Type()
|
|
for i := 0; i < rv.NumField(); i++ {
|
|
field := t.Field(i)
|
|
if field.PkgPath != "" {
|
|
continue
|
|
}
|
|
name := field.Name
|
|
if tag := field.Tag.Get("json"); tag != "" {
|
|
name = strings.Split(tag, ",")[0]
|
|
}
|
|
res[name] = relate(rv.Field(i).Interface())
|
|
}
|
|
return res
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
|
|
index(value)
|
|
output := []any{}
|
|
for i := 0; i < len(input); i++ {
|
|
output = append(output, transform(input[i]))
|
|
}
|
|
|
|
var b []byte
|
|
var err error
|
|
indent := ""
|
|
if s, ok := space.(string); ok {
|
|
indent = s
|
|
} else if i, ok := space.(int); ok {
|
|
indent = strings.Repeat(" ", i)
|
|
}
|
|
|
|
if indent != "" {
|
|
b, err = json.MarshalIndent(output, "", indent)
|
|
} else {
|
|
b, err = json.Marshal(output)
|
|
}
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b), nil
|
|
}
|
|
|
|
// Parse converts a specialized flatted string into a Go value.
|
|
func Parse(text string, reviver func(key string, value any) any) (any, error) {
|
|
var jsonInput []any
|
|
if err := json.Unmarshal([]byte(text), &jsonInput); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var wrap func(any) any
|
|
wrap = func(v any) any {
|
|
if s, ok := v.(string); ok {
|
|
return flattedIndex(s)
|
|
}
|
|
if arr, ok := v.([]any); ok {
|
|
for i, item := range arr {
|
|
arr[i] = wrap(item)
|
|
}
|
|
return arr
|
|
}
|
|
if m, ok := v.(map[string]any); ok {
|
|
for k, item := range m {
|
|
m[k] = wrap(item)
|
|
}
|
|
return m
|
|
}
|
|
return v
|
|
}
|
|
|
|
wrapped := make([]any, len(jsonInput))
|
|
for i, v := range jsonInput {
|
|
wrapped[i] = wrap(v)
|
|
}
|
|
|
|
input := make([]any, len(wrapped))
|
|
for i, v := range wrapped {
|
|
if fi, ok := v.(flattedIndex); ok {
|
|
input[i] = string(fi)
|
|
} else {
|
|
input[i] = v
|
|
}
|
|
}
|
|
|
|
if len(input) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
value := input[0]
|
|
rv := reflect.ValueOf(value)
|
|
if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Map) {
|
|
set := make(map[uintptr]bool)
|
|
set[rv.Pointer()] = true
|
|
res := loop(value, input, set)
|
|
if reviver != nil {
|
|
return revive("", res, reviver), nil
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
if reviver != nil {
|
|
return reviver("", value), nil
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func revive(key string, value any, reviver func(k string, v any) any) any {
|
|
if arr, ok := value.([]any); ok {
|
|
for i, v := range arr {
|
|
arr[i] = revive(strconv.Itoa(i), v, reviver)
|
|
}
|
|
} else if m, ok := value.(map[string]any); ok {
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
m[k] = revive(k, m[k], reviver)
|
|
}
|
|
}
|
|
return reviver(key, value)
|
|
}
|
|
|
|
func loop(value any, input []any, set map[uintptr]bool) any {
|
|
if arr, ok := value.([]any); ok {
|
|
for i, v := range arr {
|
|
if fi, ok := v.(flattedIndex); ok {
|
|
idx, _ := strconv.Atoi(string(fi))
|
|
arr[i] = ref(input[idx], input, set)
|
|
}
|
|
}
|
|
return arr
|
|
}
|
|
if m, ok := value.(map[string]any); ok {
|
|
for k, v := range m {
|
|
if fi, ok := v.(flattedIndex); ok {
|
|
idx, _ := strconv.Atoi(string(fi))
|
|
m[k] = ref(input[idx], input, set)
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
return value
|
|
}
|
|
|
|
func ref(value any, input []any, set map[uintptr]bool) any {
|
|
rv := reflect.ValueOf(value)
|
|
if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Map) {
|
|
ptr := rv.Pointer()
|
|
if !set[ptr] {
|
|
set[ptr] = true
|
|
return loop(value, input, set)
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
|
|
// ToJSON converts a generic value into a JSON serializable object without losing recursion.
|
|
func ToJSON(value any) (any, error) {
|
|
s, err := Stringify(value, nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var res any
|
|
err = json.Unmarshal([]byte(s), &res)
|
|
return res, err
|
|
}
|
|
|
|
// FromJSON converts a previously serialized object with recursion into a recursive one.
|
|
func FromJSON(value any) (any, error) {
|
|
b, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Parse(string(b), nil)
|
|
}
|