549 lines
12 KiB
Go
549 lines
12 KiB
Go
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 l’injection 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 l’objet 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 l’objet 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 l’objet et retourne vrai si elle existe.
|
||
func (o Json) DelRecursive(keys ...string) (ok bool) {
|
||
return o.del(keys...)
|
||
}
|
||
|
||
// Clone effectue une copie profonde de l’objet.
|
||
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 l’opé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
|
||
}
|