gob/collection/json/json.go

549 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package json
import (
"errors"
"io"
"sort"
"strconv"
"strings"
"gitea.zaclys.com/bvaudour/gob/collection"
"gitea.zaclys.com/bvaudour/gob/convert"
. "gitea.zaclys.com/bvaudour/gob/option"
)
var (
ErrFailedToParse = errors.New("failed to parse")
)
func isSlice(c convert.Value) bool {
return c.Is(convert.Slice)
}
func isMap(c convert.Value) bool {
return c.Is(convert.Map) && c.Type().Key().Is(convert.String)
}
func isAssignableIn(v1, v2 convert.Value) bool {
return v1.Type().AssignableTo(v2.TypeElem())
}
func searchSlice(c convert.Value, k string) (out Option[convert.Value]) {
i, err := strconv.Atoi(k)
if err == nil && i >= 0 && i < c.Len() {
out = Some(c.Index(i))
}
return
}
func searchMap(c convert.Value, k string) (out Option[convert.Value]) {
return c.MapIndexIfExists(convert.ValueOf(k))
}
func search(c convert.Value, k string) (v Option[convert.Value]) {
if isSlice(c) {
v = searchSlice(c, k)
} else if isMap(c) {
v = searchMap(c, k)
}
return
}
func searchAll(c any, keys []string) (v Option[any]) {
vv := convert.ValueOf(c)
var ok bool
for _, k := range keys {
if vv, ok = search(vv, k).Get(); !ok {
return
}
}
return Some(vv.Interface())
}
func setSlice(c convert.Value, v any, k string) (out Option[convert.Value]) {
i, err := strconv.Atoi(k)
vv := convert.ValueOf(v)
l := c.Len()
if err != nil && i >= 0 && i <= l && isAssignableIn(vv, c) {
if i < l {
c.SetIndex(i, vv)
out = Some(c)
} else {
out = Some(convert.Append(c, vv))
}
}
return
}
func setMap(c convert.Value, v any, k string) (out Option[convert.Value]) {
vv := convert.ValueOf(v)
if isAssignableIn(vv, c) {
c.SetMapIndex(convert.ValueOf(k), vv)
out = Some(c)
}
return
}
func set(c convert.Value, v any, k string) (out Option[convert.Value]) {
if isSlice(c) {
return setSlice(c, v, k)
} else if isMap(c) {
return setMap(c, v, k)
}
return
}
func setAll(c, v any, keys []string) (ok bool) {
l := len(keys)
k1, k2 := keys[l-2], keys[l-1]
cc := convert.ValueOf(c)
for _, k := range keys[:l-2] {
if cc, ok = search(cc, k).Get(); !ok {
return
}
}
var ccl convert.Value
if ccl, ok = search(cc, k1).Get(); ok {
if ccl, ok = set(ccl, v, k2).Get(); ok {
set(cc, ccl.Interface(), k1)
}
}
return
}
func delSlice(c convert.Value, k string) (out Option[convert.Value]) {
i, err := strconv.Atoi(k)
l := c.Len()
if err != nil && i >= 0 && i < l {
v := convert.SliceOf(c.Type(), l-1, l-1)
convert.Copy(v.Slice(0, i), c.Slice(0, i))
convert.Copy(v.Slice(i, l-1), c.Slice(i+1, l))
return Some(v)
}
return
}
func delMap(c convert.Value, k string) (out Option[convert.Value]) {
kk := convert.ValueOf(k)
if ok := c.MapIndexIfExists(kk).IsDefined(); ok {
c.DelMapIndex(kk)
out = Some(c)
}
return
}
func del(c convert.Value, k string) (out Option[convert.Value]) {
if isSlice(c) {
return delSlice(c, k)
} else if isMap(c) {
return delMap(c, k)
}
return
}
func delAll(c any, keys []string) (ok bool) {
l := len(keys)
k1, k2 := keys[l-2], keys[l-1]
cc := convert.ValueOf(c)
for _, k := range keys[:l-2] {
if cc, ok = search(cc, k).Get(); !ok {
return
}
}
var ccl convert.Value
if ccl, ok = search(cc, k1).Get(); ok {
if ccl, ok = del(ccl, k2).Get(); ok {
set(cc, ccl.Interface(), k1)
}
}
return
}
func flat(v convert.Value, prefix ...string) (keys [][]string, values []any) {
if isSlice(v) {
it, l := v.SliceRange(), len(prefix)
for it.Next() {
k := make([]string, l+1)
copy(k[:l], prefix)
k[l] = strconv.Itoa(it.Index())
kk, vv := flat(it.Value(), k...)
collection.Add(&keys, kk...)
collection.Add(&values, vv...)
}
} else if isMap(v) {
it, l := v.MapRange(), len(prefix)
for it.Next() {
k := make([]string, l+1)
copy(k[:l], prefix)
k[l] = it.Key().String()
kk, vv := flat(it.Value(), k...)
collection.Add(&keys, kk...)
collection.Add(&values, vv...)
}
} else {
collection.Add(&keys, prefix)
collection.Add(&values, v.Interface())
}
return
}
func createRec(o Json, keys []string) Json {
e := o
for _, k := range keys {
v, ok := o[k]
if ok {
if m, ok := v.(Json); ok {
e = m
continue
}
}
m := make(Json)
e[k] = m
e = m
}
return o
}
func unflat(v convert.Value, split SplitFunc) (out convert.Value) {
if isSlice(v) {
it := v.SliceRange()
for it.Next() {
i, e := it.Index(), unflat(it.Value(), split)
v.SetIndex(i, e)
}
return v
} else if isMap(v) {
result := make(Json)
it := v.MapRange()
for it.Next() {
k, e := it.Key().String(), unflat(it.Value(), split)
spl := SplitKey(k, split)
if keys, ok := spl.Ok(); ok {
result = createRec(result, keys)
result.SetRecursive(e.Interface(), keys...)
} else {
result[k] = e.Interface()
}
}
return convert.ValueOf(result)
}
return v
}
func sSlice(c convert.Value) convert.Value {
it := c.SliceRange()
for it.Next() {
i, v := it.Index(), slicify(it.Value())
c.SetIndex(i, v)
}
return c
}
func canSlice(c convert.Value) (out Option[map[int]string]) {
if !isMap(c) {
return
}
var keys []int
skeys := make(map[int]string)
it := c.MapRange()
for it.Next() {
k := it.Key().String()
i, err := strconv.Atoi(k)
if err != nil || i < 0 {
return
}
collection.Add(&keys, i)
skeys[i] = k
}
sort.Ints(keys)
for i, j := range keys {
if i != j {
return
}
}
return Some(skeys)
}
func sMap(c convert.Value) convert.Value {
if keys, ok := canSlice(c).Get(); ok {
sl := convert.SliceOf(convert.SliceTypeOf(c.TypeElem()), len(keys), len(keys))
it := sl.SliceRange()
for it.Next() {
i := it.Index()
k := keys[i]
v := slicify(c.MapIndex(convert.ValueOf(k)))
sl.SetIndex(i, v)
}
return sl
}
it := c.MapRange()
for it.Next() {
k, v := it.Key(), slicify(it.Value())
c.SetMapIndex(k, v)
}
return c
}
func slicify(v convert.Value) convert.Value {
if isSlice(v) {
return sSlice(v)
} else if isMap(v) {
return sMap(v)
}
return v
}
// Json représente un simple objet JSON.
type Json map[string]any
// SplitFunc est une fonction qui découpe une clé en sous-clés.
//
// Paramètres :
// - key : la clé à découper
//
// Sortie :
// - current : la sous-clé parsée
// - next : la partie de clé qui reste à parser
// - err : une erreur retournée lors du parsage ou io.EOF si le parsage est terminé
type SplitFunc func(key string) (current, next string, err error)
// MergeFunc est la transformée inverse de SplitFunc.
type MergeFunc func(keys []string) (key string)
// SplitDot découpe une clé qui est sous la forme a.b.c…
func SplitDot(key string) (current, next string, err error) {
i := strings.Index(key, ".")
if i < 0 {
current, err = key, io.EOF
} else {
current, next = key[:i], key[i+1:]
}
return
}
// SplitBracket découpe une clé qui est sous la forme a[b][c]…
func SplitBracket(key string) (current, next string, err error) {
i0 := strings.Index(key, "[")
i1 := strings.Index(key, "]")
if i0 < 0 {
if i1 >= 0 {
err = ErrFailedToParse
} else {
current, err = key, io.EOF
}
} else if i0 >= i1 || (len(key) > i1+1 && key[i1+1] != '[') {
err = ErrFailedToParse
} else {
current, next = key[:i0], key[i0+1:i1]+key[i1:]
}
return
}
// SplitKey découpe la clé selon la fonction de découpage fournie.
func SplitKey(key string, split SplitFunc) (out Result[[]string]) {
current, next := key, ""
var err error
var keys []string
for current, next, err = split(current); err == nil; current, next, err = split(current) {
collection.Add(&keys, current)
current = next
}
if err == io.EOF {
return Ok(keys)
}
return Err[[]string](err)
}
// MergeDot crée une clé sous la forme a.b.c…
func MergeDot(keys []string) string {
return strings.Join(keys, ".")
}
// MergeBracket crée une clé sous la forme a[b][c]…
func MergeBracket(keys []string) (key string) {
if len(keys) == 0 {
return
}
key, keys = keys[0], keys[1:]
for _, k := range keys {
key += "[" + k + "]"
}
return
}
func (o Json) get(keys ...string) (v Option[any]) {
switch len(keys) {
case 0:
v = Some[any](o)
case 1:
v = Some[any](o[keys[0]])
default:
v = searchAll(o, keys)
}
return
}
// Get retourne la valeur de la clé.
// Si une fonction de découpage est fournie, la clé est recherchée de façon récursive après découpage.
func (o Json) Get(key string, split ...SplitFunc) (value Option[any]) {
if len(split) == 0 {
return o.get(key)
}
if keys, ok := SplitKey(key, split[0]).Ok(); ok {
return o.get(keys...)
}
return
}
// GetRecursive retourne la valeur de la clé (de façon récursive).
func (o Json) GetRecursive(keys ...string) (value Option[any]) {
return o.get(keys...)
}
// Parse injecte la valeur associée à la clé dans out, si possible,
// et retourne vrai si linjection a été possible.
// out doit être un pointeur.
// Si split est fourni la clé est découpée.
func (o Json) Parse(out any, key string, split ...SplitFunc) (ok bool) {
var v any
if v, ok = o.Get(key, split...).Get(); ok {
ok = convert.Convert(out, v)
}
return
}
// ParseRecurive agit comme Parse, récursivement.
func (o Json) ParseRecursive(out any, keys ...string) (ok bool) {
var v any
if v, ok = o.GetRecursive(keys...).Get(); ok {
ok = convert.Convert(out, v)
}
return
}
// SafeParse agit comme Parse mais la valeur trouvée doit être compatible avec le type de out.
func (o Json) SafeParse(out any, key string, split ...SplitFunc) (ok bool) {
var v any
if v, ok = o.Get(key, split...).Get(); ok {
ok = convert.Convert(out, v, true)
}
return
}
// SafeParseRecursive agit comme SafeParse, récursivement.
func (o Json) SafeParseRecursive(out any, keys ...string) (ok bool) {
var v any
if v, ok = o.GetRecursive(keys...).Get(); ok {
ok = convert.Convert(out, v, true)
}
return
}
func (o Json) set(value any, keys ...string) (ok bool) {
l := len(keys)
switch l {
case 0:
case 1:
o[keys[0]], ok = value, true
default:
ok = setAll(o, value, keys)
}
return
}
// Set insère la valeur dans lobjet et retourne vrai si la valeur a été insérée.
// Si la fonction de découpage est fournie, la clé est découpée.
func (o Json) Set(value any, key string, split ...SplitFunc) (ok bool) {
if len(split) == 0 {
return o.set(value, key)
}
var keys []string
if keys, ok = SplitKey(key, split[0]).Ok(); ok {
return o.set(value, keys...)
}
return
}
// SetRecursive insère la valeur de façon récursive dans lobjet et retourne vrai si elle existe.
func (o Json) SetRecursive(value any, keys ...string) (ok bool) {
return o.set(value, keys...)
}
func (o Json) del(keys ...string) (ok bool) {
l := len(keys)
switch l {
case 0:
case 1:
if ok = o.Get(keys[0]).IsDefined(); ok {
delete(o, keys[0])
}
default:
ok = delAll(o, keys)
}
return
}
// Si la fonction de découpage est fournie, la clé est découpée.
func (o Json) Del(key string, split ...SplitFunc) (ok bool) {
if len(split) == 0 {
return o.del(key)
}
var keys []string
if keys, ok = SplitKey(key, split[0]).Ok(); ok {
return o.del(keys...)
}
return
}
// DelRecursive supprimer la clé de façon récursive dans lobjet et retourne vrai si elle existe.
func (o Json) DelRecursive(keys ...string) (ok bool) {
return o.del(keys...)
}
// Clone effectue une copie profonde de lobjet.
func (o Json) Clone(deep ...bool) Json {
c := make(Json)
for k, v := range o {
c.Set(convert.Clone(v, deep...), k)
}
return c
}
// Fusion ajoute toutes les entrées de o2 dans o1 et retourne o1
func (o1 Json) Fusion(o2 Json) Json {
for k, v := range o2 {
o1[k] = v
}
return o1
}
// Flat aplatit la structure du Json.
func (o Json) Flat(merge MergeFunc) (out Json) {
keys, values := flat(convert.ValueOf(o))
out = make(Json)
for i, k := range keys {
out[merge(k)] = values[i]
}
return
}
// Unflat est lopération inverse de Flat.
func (o Json) Unflat(split SplitFunc) (out Json) {
vo := unflat(convert.ValueOf(o), split)
out = vo.Interface().(Json)
for k, v := range out {
out[k] = slicify(convert.ValueOf(v))
}
return
}