package urlenc import ( "net/url" "encoding/json" "strings" "fmt" ) func Unmarshal(bts []byte, v any) error { unesc, err := url.QueryUnescape(string(bts)) if err != nil { return err } mp := map[string] any{} err = parseStr(unesc, mp) if err != nil { return err } js, err := json.Marshal(mp) if err != nil { return err } err = json.Unmarshal(js, v) if err != nil { return err } return nil } func parseStr(encodedString string, result map[string]any) error { // build nested map. var build func(map[string]any, []string, any) error build = func(result map[string]any, keys []string, value any) error { length := len(keys) // trim '," key := strings.Trim(keys[0], "'\"") if length == 1 { result[key] = value return nil } // The end is slice. like f[], f[a][] if keys[1] == "" && length == 2 { // todo nested slice if key == "" { return nil } val, ok := result[key] if !ok { result[key] = []any{value} return nil } children, ok := val.([]any) if !ok { return fmt.Errorf("expected type '[]any' for key '%s', but got '%T'", key, val) } result[key] = append(children, value) return nil } // The end is slice + map. like f[][a] if keys[1] == "" && length > 2 && keys[2] != "" { val, ok := result[key] if !ok { result[key] = []any{} val = result[key] } children, ok := val.([]any) if !ok { return fmt.Errorf("expected type '[]any' for key '%s', but got '%T'", key, val) } if l := len(children); l > 0 { if child, ok := children[l-1].(map[string]any); ok { if _, ok := child[keys[2]]; !ok { _ = build(child, keys[2:], value) return nil } } } child := map[string]any{} _ = build(child, keys[2:], value) result[key] = append(children, child) return nil } // map. like f[a], f[a][b] val, ok := result[key] if !ok { result[key] = map[string]any{} val = result[key] } children, ok := val.(map[string]any) if !ok { return fmt.Errorf("expected type 'map[string]any' for key '%s', but got '%T'", key, val) } return build(children, keys[1:], value) } // split encodedString. parts := strings.Split(encodedString, "&") for _, part := range parts { pos := strings.Index(part, "=") if pos <= 0 { continue } key, err := url.QueryUnescape(part[:pos]) if err != nil { return err } for key[0] == ' ' { key = key[1:] } if key == "" || key[0] == '[' { continue } value, err := url.QueryUnescape(part[pos+1:]) if err != nil { return err } // split into multiple keys var keys []string left := 0 for i, k := range key { if k == '[' && left == 0 { left = i } else if k == ']' { if left > 0 { if len(keys) == 0 { keys = append(keys, key[:left]) } keys = append(keys, key[left+1:i]) left = 0 if i+1 < len(key) && key[i+1] != '[' { break } } } } if len(keys) == 0 { keys = append(keys, key) } // first key first := "" for i, chr := range keys[0] { if chr == ' ' || chr == '.' || chr == '[' { first += "_" } else { first += string(chr) } if chr == '[' { first += keys[0][i+1:] break } } keys[0] = first // build nested map if err := build(result, keys, value); err != nil { return err } } return nil }