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 }