Module collection (3)

This commit is contained in:
Benjamin VAUDOUR 2023-09-23 20:11:25 +02:00
parent 53d7e6c217
commit 6637db7be4
3 changed files with 668 additions and 0 deletions

106
collection/json/convert.go Normal file
View File

@ -0,0 +1,106 @@
package json
import (
j "encoding/json"
"io"
. "gitea.zaclys.com/bvaudour/gob/option"
)
// JsonDecoder permet de parser une entrée brute en un objet Json ou un tableau de Json.
type JsonDecoder struct {
d *j.Decoder
}
// NewJsonDecoder retourne un décodeur pour la donnée dentrée spécifiée.
func NewJsonDecoder(r io.Reader) *JsonDecoder { return &JsonDecoder{d: j.NewDecoder(r)} }
// Decode retourne un objet Json décodé.
func (dec *JsonDecoder) Decode() Result[Json] {
o := make(Json)
if err := dec.d.Decode(&o); err != nil {
return Err[Json](err)
}
return Ok(o)
}
// DecodeSlice retourne un tableau de Json décodé.
func (dec *JsonDecoder) DecodeSlice() Result[Slice] {
var sl Slice
if err := dec.d.Decode(&sl); err != nil {
return Err[Slice](err)
}
return Ok(sl)
}
// Decode décode une entrée brute et la fusionne avec lobjet Json.
func (o Json) Decode(r io.Reader) Result[Json] {
dec := NewJsonDecoder(r)
result := dec.Decode()
if o2, ok := result.Ok(); ok {
o = o.Fusion(o2)
return Ok(o)
}
return result
}
// Decode décode une entrée brute et la fusionne avec le tableau de Json.
func (sl *Slice) Decode(r io.Reader) Result[Slice] {
dec := NewJsonDecoder(r)
result := dec.DecodeSlice()
if sl2, ok := result.Ok(); ok {
return Ok(sl.Add(sl2...))
}
return result
}
// JsonEncoder permet de convertir un objet Json ou un tableau de Json au format brut.
type JsonEncoder struct {
e *j.Encoder
}
// NewJsonEncoder fournit un encodeur pour la sortie fournie.
func NewJsonEncoder(w io.Writer) *JsonEncoder { return &JsonEncoder{e: j.NewEncoder(w)} }
// Encode encode un objet Json.
func (enc *JsonEncoder) Encode(o Json) error { return enc.e.Encode(o) }
// EncodeSlice encode un tableau Json.
func (enc *JsonEncoder) EncodeSlice(sl Slice) error { return enc.e.Encode(sl) }
// SetEscapeHTML configure lencodeur pour que les caractère HTML soient échappés si on est vrai.
// Ainsi, les caractères &, <, et > sont transformés respectivement en \u0026, \u003c et \u003e
// Cela permet déviter des problèmes de sécurité lorsque du html est embarqué dans du Json.
func (enc *JsonEncoder) SetEscapeHTML(on bool) { enc.e.SetEscapeHTML(on) }
// SetIndent définit le préfixe et lindentation du Json à la sortie.
// Cela permet de rendre un Json dans un format lisible (ie. non minifié).
func (enc *JsonEncoder) SetIndent(prefix, indent string) { enc.e.SetIndent(prefix, indent) }
// Encode minifie le Json dans la sortie donnée.
func (o Json) Encode(w io.Writer) error {
enc := NewJsonEncoder(w)
return enc.Encode(o)
}
// EncodeHuman encode le Json dans un format humainement lisible (indentation de deux espaces).
func (o Json) EncodeHuman(w io.Writer) error {
enc := NewJsonEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(o)
}
// Encode minifie le tableau de Json dans la sortie donnée.
func (sl Slice) Encode(w io.Writer) error {
enc := NewJsonEncoder(w)
return enc.EncodeSlice(sl)
}
// EncodeHuman encode le tableau de Json dans un format humainement lisible (indentation de deux espaces).
func (sl Slice) EncodeHuman(w io.Writer) error {
enc := NewJsonEncoder(w)
enc.SetIndent("", " ")
return enc.EncodeSlice(sl)
}

548
collection/json/json.go Normal file
View File

@ -0,0 +1,548 @@
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
}

View File

@ -0,0 +1,14 @@
package json
// Slice est un tableau dobjets JSON.
type Slice []Json
func (sl *Slice) Add(objects ...Json) Slice {
*sl = append(*sl, objects...)
return *sl
}
func (sl *Slice) Insert(objects ...Json) Slice {
*sl = append(objects, (*sl)...)
return *sl
}