gob/collection/json/json.go

549 lines
12 KiB
Go
Raw Permalink Normal View History

2023-09-23 18:11:25 +00:00
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
}