Repository: jeremywohl/flatten
Branch: master
Commit: 07e4a09fb8e4
Files: 13
Total size: 26.2 KB
Directory structure:
gitextract_fuvuek_y/
├── .gitignore
├── LICENSE
├── README.md
├── TODO
├── doc.go
├── flatten.go
├── flatten_test.go
├── go.mod
└── v2/
├── README.md
├── doc.go
├── flatten.go
├── flatten_test.go
└── go.mod
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Jeremy Wohl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
flatten
=======
[](https://pkg.go.dev/github.com/jeremywohl/flatten)
**Note: development continues in [v2](/v2)**
Flatten makes flat, one-dimensional maps from arbitrarily nested ones.
It turns map keys into compound
names, in four default styles: dotted (`a.b.1.c`), path-like (`a/b/1/c`), Rails (`a[b][1][c]`), or with underscores (`a_b_1_c`). Alternatively, you can pass a custom style.
It takes input as either JSON strings or
Go structures. It knows how to traverse these JSON types: objects/maps, arrays and scalars.
You can flatten JSON strings.
```go
nested := `{
"one": {
"two": [
"2a",
"2b"
]
},
"side": "value"
}`
flat, err := flatten.FlattenString(nested, "", flatten.DotStyle)
// output: `{ "one.two.0": "2a", "one.two.1": "2b", "side": "value" }`
```
Or Go maps directly.
```go
nested := map[string]interface{}{
"a": "b",
"c": map[string]interface{}{
"d": "e",
"f": "g",
},
"z": 1.4567,
}
flat, err := flatten.Flatten(nested, "", flatten.RailsStyle)
// output:
// map[string]interface{}{
// "a": "b",
// "c[d]": "e",
// "c[f]": "g",
// "z": 1.4567,
// }
```
Let's try a custom style, with the first example above.
```go
emdash := flatten.SeparatorStyle{Middle: "--"}
flat, err := flatten.FlattenString(nested, "", emdash)
// output: `{ "one--two--0": "2a", "one--two--1": "2b", "side": "value" }`
```
See [godoc](https://godoc.org/github.com/jeremywohl/flatten) for API.
================================================
FILE: TODO
================================================
test: all Go scalar types
todo: initial list vs map
todo: fail properly with alternate types
todo: support structs and pointers?
================================================
FILE: doc.go
================================================
// Flatten makes flat, one-dimensional maps from arbitrarily nested ones.
//
// It turns map keys into compound
// names, in four default styles: dotted (`a.b.1.c`), path-like (`a/b/1/c`), Rails (`a[b][1][c]`),
// or with underscores (`a_b_1_c`). Alternatively, you can pass a custom style.
//
// It takes input as either JSON strings or
// Go structures. It knows how to traverse these JSON types: objects/maps, arrays and scalars.
//
// You can flatten JSON strings.
//
// nested := `{
// "one": {
// "two": [
// "2a",
// "2b"
// ]
// },
// "side": "value"
// }`
//
// flat, err := flatten.FlattenString(nested, "", flatten.DotStyle)
//
// // output: `{ "one.two.0": "2a", "one.two.1": "2b", "side": "value" }`
//
// Or Go maps directly.
//
// nested := map[string]interface{}{
// "a": "b",
// "c": map[string]interface{}{
// "d": "e",
// "f": "g",
// },
// "z": 1.4567,
// }
//
// flat, err := flatten.Flatten(nested, "", flatten.RailsStyle)
//
// // output:
// // map[string]interface{}{
// // "a": "b",
// // "c[d]": "e",
// // "c[f]": "g",
// // "z": 1.4567,
// // }
//
// Let's try a custom style, with the first example above.
//
// emdash := flatten.SeparatorStyle{Middle: "--"}
// flat, err := flatten.FlattenString(nested, "", emdash)
//
// // output: `{ "one--two--0": "2a", "one--two--1": "2b", "side": "value" }`
//
package flatten
================================================
FILE: flatten.go
================================================
package flatten
import (
"encoding/json"
"errors"
"regexp"
"strconv"
)
// The style of keys. If there is an input with two
// nested keys "f" and "g", with "f" at the root,
// { "f": { "g": ... } }
// the output will be the concatenation
// f{Middle}{Before}g{After}...
// Any struct element may be blank.
// If you use Middle, you will probably leave Before & After blank, and vice-versa.
// See examples in flatten_test.go and the "Default styles" here.
type SeparatorStyle struct {
Before string // Prepend to key
Middle string // Add between keys
After string // Append to key
}
// Default styles
var (
// Separate nested key components with dots, e.g. "a.b.1.c.d"
DotStyle = SeparatorStyle{Middle: "."}
// Separate with path-like slashes, e.g. a/b/1/c/d
PathStyle = SeparatorStyle{Middle: "/"}
// Separate ala Rails, e.g. "a[b][c][1][d]"
RailsStyle = SeparatorStyle{Before: "[", After: "]"}
// Separate with underscores, e.g. "a_b_1_c_d"
UnderscoreStyle = SeparatorStyle{Middle: "_"}
)
// Nested input must be a map or slice
var NotValidInputError = errors.New("Not a valid input: map or slice")
// Flatten generates a flat map from a nested one. The original may include values of type map, slice and scalar,
// but not struct. Keys in the flat map will be a compound of descending map keys and slice iterations.
// The presentation of keys is set by style. A prefix is joined to each key.
func Flatten(nested map[string]interface{}, prefix string, style SeparatorStyle) (map[string]interface{}, error) {
flatmap := make(map[string]interface{})
err := flatten(true, flatmap, nested, prefix, style)
if err != nil {
return nil, err
}
return flatmap, nil
}
// JSON nested input must be a map
var NotValidJsonInputError = errors.New("Not a valid input, must be a map")
var isJsonMap = regexp.MustCompile(`^\s*\{`)
// FlattenString generates a flat JSON map from a nested one. Keys in the flat map will be a compound of
// descending map keys and slice iterations. The presentation of keys is set by style. A prefix is joined
// to each key.
func FlattenString(nestedstr, prefix string, style SeparatorStyle) (string, error) {
if !isJsonMap.MatchString(nestedstr) {
return "", NotValidJsonInputError
}
var nested map[string]interface{}
err := json.Unmarshal([]byte(nestedstr), &nested)
if err != nil {
return "", err
}
flatmap, err := Flatten(nested, prefix, style)
if err != nil {
return "", err
}
flatb, err := json.Marshal(&flatmap)
if err != nil {
return "", err
}
return string(flatb), nil
}
func flatten(top bool, flatMap map[string]interface{}, nested interface{}, prefix string, style SeparatorStyle) error {
assign := func(newKey string, v interface{}) error {
switch v.(type) {
case map[string]interface{}, []interface{}:
if err := flatten(false, flatMap, v, newKey, style); err != nil {
return err
}
default:
flatMap[newKey] = v
}
return nil
}
switch nested.(type) {
case map[string]interface{}:
for k, v := range nested.(map[string]interface{}) {
newKey := enkey(top, prefix, k, style)
assign(newKey, v)
}
case []interface{}:
for i, v := range nested.([]interface{}) {
newKey := enkey(top, prefix, strconv.Itoa(i), style)
assign(newKey, v)
}
default:
return NotValidInputError
}
return nil
}
func enkey(top bool, prefix, subkey string, style SeparatorStyle) string {
key := prefix
if top {
key += subkey
} else {
key += style.Before + style.Middle + subkey + style.After
}
return key
}
================================================
FILE: flatten_test.go
================================================
package flatten
import (
"encoding/json"
"reflect"
"strings"
"testing"
"unicode"
)
func TestFlatten(t *testing.T) {
cases := []struct {
test string
want map[string]interface{}
prefix string
style SeparatorStyle
}{
// 1
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"foo.jim": "bean",
"fee": "bar",
"n1.alist.0": "a",
"n1.alist.1": "b",
"n1.alist.2": "c",
"n1.alist.3.d": "other",
"n1.alist.3.e": "another",
"number": 1.4567,
"bool": true,
},
"",
DotStyle,
},
// 2
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
}
}`,
map[string]interface{}{
"foo[jim]": "bean",
"fee": "bar",
"n1[alist][0]": "a",
"n1[alist][1]": "b",
"n1[alist][2]": "c",
"n1[alist][3][d]": "other",
"n1[alist][3][e]": "another",
},
"",
RailsStyle,
},
// 3
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"foo/jim": "bean",
"fee": "bar",
"n1/alist/0": "a",
"n1/alist/1": "b",
"n1/alist/2": "c",
"n1/alist/3/d": "other",
"n1/alist/3/e": "another",
"number": 1.4567,
"bool": true,
},
"",
PathStyle,
},
// 4
{
`{ "a": { "b": "c" }, "e": "f" }`,
map[string]interface{}{
"p:a.b": "c",
"p:e": "f",
},
"p:",
DotStyle,
},
// 5
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"foo_jim": "bean",
"fee": "bar",
"n1_alist_0": "a",
"n1_alist_1": "b",
"n1_alist_2": "c",
"n1_alist_3_d": "other",
"n1_alist_3_e": "another",
"number": 1.4567,
"bool": true,
},
"",
UnderscoreStyle,
},
// 6 -- try a prefix
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"flag-foo_jim": "bean",
"flag-fee": "bar",
"flag-n1_alist_0": "a",
"flag-n1_alist_1": "b",
"flag-n1_alist_2": "c",
"flag-n1_alist_3_d": "other",
"flag-n1_alist_3_e": "another",
"flag-number": 1.4567,
"flag-bool": true,
},
"flag-",
UnderscoreStyle,
},
}
for i, test := range cases {
var m interface{}
err := json.Unmarshal([]byte(test.test), &m)
if err != nil {
t.Errorf("%d: failed to unmarshal test: %v", i+1, err)
continue
}
got, err := Flatten(m.(map[string]interface{}), test.prefix, test.style)
if err != nil {
t.Errorf("%d: failed to flatten: %v", i+1, err)
continue
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("%d: mismatch, got: %v wanted: %v", i+1, got, test.want)
}
}
}
func TestFlattenString(t *testing.T) {
cases := []struct {
test string
want string
prefix string
style SeparatorStyle
err error
}{
// 1
{
`{ "a": "b" }`,
`{ "a": "b" }`,
"",
DotStyle,
nil,
},
// 2
{
`{ "a": { "b" : { "c" : { "d" : "e" } } }, "number": 1.4567, "bool": true }`,
`{ "a.b.c.d": "e", "bool": true, "number": 1.4567 }`,
"",
DotStyle,
nil,
},
// 3
{
`{ "a": { "b" : { "c" : { "d" : "e" } } }, "number": 1.4567, "bool": true }`,
`{ "a/b/c/d": "e", "bool": true, "number": 1.4567 }`,
"",
PathStyle,
nil,
},
// 4
{
`{ "a": { "b" : { "c" : { "d" : "e" } } } }`,
`{ "a--b--c--d": "e" }`,
"",
SeparatorStyle{Middle: "--"}, // emdash
nil,
},
// 5
{
`{ "a": { "b" : { "c" : { "d" : "e" } } } }`,
`{ "a(b)(c)(d)": "e" }`,
"",
SeparatorStyle{Before: "(", After: ")"}, // paren groupings
nil,
},
// 6 -- with leading whitespace
{
`
{ "a": { "b" : { "c" : { "d" : "e" } } } }`,
`{ "a(b)(c)(d)": "e" }`,
"",
SeparatorStyle{Before: "(", After: ")"}, // paren groupings
nil,
},
//
// Valid JSON text, but invalid for FlattenString
//
// 7
{
`[ "a": { "b": "c" }, "d" ]`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 8
{
``,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 9
{
`astring`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 10
{
`false`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 11
{
`42`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 12 -- prior to version 1.0.1, this was accepted & unmarshalled as an empty map, finally returning `{}`.
{
`null`,
`{}`,
"",
PathStyle,
NotValidJsonInputError,
},
// 13 -- try a prefix
{
`{ "a": { "b" : { "c" : { "d" : "e" } } }, "number": 1.4567, "bool": true }`,
`{ "flag-a.b.c.d": "e", "flag-bool": true, "flag-number": 1.4567 }`,
"flag-",
DotStyle,
nil,
},
}
for i, test := range cases {
got, err := FlattenString(test.test, test.prefix, test.style)
if err != test.err {
t.Errorf("%d: error mismatch, got: [%v], wanted: [%v]", i+1, err, test.err)
continue
}
if err != nil {
continue
}
nixws := func(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}
if got != strings.Map(nixws, test.want) {
t.Errorf("%d: mismatch, got: %v wanted: %v", i+1, got, test.want)
}
}
}
================================================
FILE: go.mod
================================================
module github.com/jeremywohl/flatten
================================================
FILE: v2/README.md
================================================
flatten
=======
[](https://pkg.go.dev/pkg.go.dev/github.com/jeremywohl/flatten/v2)
Flatten makes flat, one-dimensional maps from arbitrarily nested ones.
It turns map keys into compound
names, in four default styles: dotted (`a.b.1.c`), path-like (`a/b/1/c`), Rails (`a[b][1][c]`), or with underscores (`a_b_1_c`). Alternatively, you can pass a custom style.
It takes input as either JSON strings or
Go structures. It knows how to traverse these JSON types: objects/maps, arrays and scalars.
You can flatten JSON strings.
```go
nested := `{
"one": {
"two": [
"2a",
"2b"
]
},
"side": "value"
}`
flat, err := flatten.FlattenString(nested, "", flatten.DotStyle)
// output: `{ "one.two.0": "2a", "one.two.1": "2b", "side": "value" }`
```
Or Go maps directly.
```go
nested := map[string]interface{}{
"a": "b",
"c": map[string]interface{}{
"d": "e",
"f": "g",
},
"z": 1.4567,
}
flat, err := flatten.Flatten(nested, "", flatten.RailsStyle)
// output:
// map[string]interface{}{
// "a": "b",
// "c[d]": "e",
// "c[f]": "g",
// "z": 1.4567,
// }
```
Let's try a custom style, with the first example above.
```go
emdash := flatten.SeparatorStyle{Middle: "--"}
flat, err := flatten.FlattenString(nested, "", emdash)
// output: `{ "one--two--0": "2a", "one--two--1": "2b", "side": "value" }`
```
See [godoc](https://godoc.org/github.com/jeremywohl/flatten) for API.
================================================
FILE: v2/doc.go
================================================
// Flatten makes flat, one-dimensional maps from arbitrarily nested ones.
//
// It turns map keys into compound
// names, in four default styles: dotted (`a.b.1.c`), path-like (`a/b/1/c`), Rails (`a[b][1][c]`),
// or with underscores (`a_b_1_c`). Alternatively, you can pass a custom style.
//
// It takes input as either JSON strings or
// Go structures. It knows how to traverse these JSON types: objects/maps, arrays and scalars.
//
// You can flatten JSON strings.
//
// nested := `{
// "one": {
// "two": [
// "2a",
// "2b"
// ]
// },
// "side": "value"
// }`
//
// flat, err := flatten.FlattenString(nested, "", flatten.DotStyle)
//
// // output: `{ "one.two.0": "2a", "one.two.1": "2b", "side": "value" }`
//
// Or Go maps directly.
//
// nested := map[string]interface{}{
// "a": "b",
// "c": map[string]interface{}{
// "d": "e",
// "f": "g",
// },
// "z": 1.4567,
// }
//
// flat, err := flatten.Flatten(nested, "", flatten.RailsStyle)
//
// // output:
// // map[string]interface{}{
// // "a": "b",
// // "c[d]": "e",
// // "c[f]": "g",
// // "z": 1.4567,
// // }
//
// Let's try a custom style, with the first example above.
//
// emdash := flatten.SeparatorStyle{Middle: "--"}
// flat, err := flatten.FlattenString(nested, "", emdash)
//
// // output: `{ "one--two--0": "2a", "one--two--1": "2b", "side": "value" }`
//
package flatten
================================================
FILE: v2/flatten.go
================================================
package flatten
import (
"encoding/json"
"errors"
"regexp"
"strconv"
)
// The style of keys. If there is an input with two
// nested keys "f" and "g", with "f" at the root,
// { "f": { "g": ... } }
// the output will be the concatenation
// f{Middle}{Before}g{After}...
// Any struct element may be blank.
// If you use Middle, you will probably leave Before & After blank, and vice-versa.
// See examples in flatten_test.go and the "Default styles" here.
type SeparatorStyle struct {
Before string // Prepend to key
Middle string // Add between keys
After string // Append to key
}
// Default styles
var (
// Separate nested key components with dots, e.g. "a.b.1.c.d"
DotStyle = SeparatorStyle{Middle: "."}
// Separate with path-like slashes, e.g. a/b/1/c/d
PathStyle = SeparatorStyle{Middle: "/"}
// Separate ala Rails, e.g. "a[b][c][1][d]"
RailsStyle = SeparatorStyle{Before: "[", After: "]"}
// Separate with underscores, e.g. "a_b_1_c_d"
UnderscoreStyle = SeparatorStyle{Middle: "_"}
)
// Nested input must be a map or slice
var NotValidInputError = errors.New("Not a valid input: map or slice")
// Flatten generates a flat map from a nested one. The original may include values of type map, slice and scalar,
// but not struct. Keys in the flat map will be a compound of descending map keys and slice iterations.
// The presentation of keys is set by style. A prefix is joined to each key.
func Flatten(nested map[string]interface{}, prefix string, style SeparatorStyle) (map[string]interface{}, error) {
flatmap := make(map[string]interface{})
err := flatten(true, flatmap, nested, prefix, style)
if err != nil {
return nil, err
}
return flatmap, nil
}
// JSON nested input must be a map
var NotValidJsonInputError = errors.New("Not a valid input, must be a map")
var isJsonMap = regexp.MustCompile(`^\s*\{`)
// FlattenString generates a flat JSON map from a nested one. Keys in the flat map will be a compound of
// descending map keys and slice iterations. The presentation of keys is set by style. A prefix is joined
// to each key.
func FlattenString(nestedstr, prefix string, style SeparatorStyle) (string, error) {
if !isJsonMap.MatchString(nestedstr) {
return "", NotValidJsonInputError
}
var nested map[string]interface{}
err := json.Unmarshal([]byte(nestedstr), &nested)
if err != nil {
return "", err
}
flatmap, err := Flatten(nested, prefix, style)
if err != nil {
return "", err
}
flatb, err := json.Marshal(&flatmap)
if err != nil {
return "", err
}
return string(flatb), nil
}
func flatten(top bool, flatMap map[string]interface{}, nested interface{}, prefix string, style SeparatorStyle) error {
assign := func(newKey string, v interface{}) error {
switch v.(type) {
case map[string]interface{}, []interface{}:
if err := flatten(false, flatMap, v, newKey, style); err != nil {
return err
}
default:
flatMap[newKey] = v
}
return nil
}
switch nested.(type) {
case map[string]interface{}:
for k, v := range nested.(map[string]interface{}) {
newKey := enkey(top, prefix, k, style)
assign(newKey, v)
}
case []interface{}:
for i, v := range nested.([]interface{}) {
newKey := enkey(top, prefix, strconv.Itoa(i), style)
assign(newKey, v)
}
default:
return NotValidInputError
}
return nil
}
func enkey(top bool, prefix, subkey string, style SeparatorStyle) string {
key := prefix
if top {
key += subkey
} else {
key += style.Before + style.Middle + subkey + style.After
}
return key
}
================================================
FILE: v2/flatten_test.go
================================================
package flatten
import (
"encoding/json"
"reflect"
"strings"
"testing"
"unicode"
)
func TestFlatten(t *testing.T) {
cases := []struct {
test string
want map[string]interface{}
prefix string
style SeparatorStyle
}{
// 1
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"foo.jim": "bean",
"fee": "bar",
"n1.alist.0": "a",
"n1.alist.1": "b",
"n1.alist.2": "c",
"n1.alist.3.d": "other",
"n1.alist.3.e": "another",
"number": 1.4567,
"bool": true,
},
"",
DotStyle,
},
// 2
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
}
}`,
map[string]interface{}{
"foo[jim]": "bean",
"fee": "bar",
"n1[alist][0]": "a",
"n1[alist][1]": "b",
"n1[alist][2]": "c",
"n1[alist][3][d]": "other",
"n1[alist][3][e]": "another",
},
"",
RailsStyle,
},
// 3
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"foo/jim": "bean",
"fee": "bar",
"n1/alist/0": "a",
"n1/alist/1": "b",
"n1/alist/2": "c",
"n1/alist/3/d": "other",
"n1/alist/3/e": "another",
"number": 1.4567,
"bool": true,
},
"",
PathStyle,
},
// 4
{
`{ "a": { "b": "c" }, "e": "f" }`,
map[string]interface{}{
"p:a.b": "c",
"p:e": "f",
},
"p:",
DotStyle,
},
// 5
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"foo_jim": "bean",
"fee": "bar",
"n1_alist_0": "a",
"n1_alist_1": "b",
"n1_alist_2": "c",
"n1_alist_3_d": "other",
"n1_alist_3_e": "another",
"number": 1.4567,
"bool": true,
},
"",
UnderscoreStyle,
},
// 6 -- try a prefix
{
`{
"foo": {
"jim":"bean"
},
"fee": "bar",
"n1": {
"alist": [
"a",
"b",
"c",
{
"d": "other",
"e": "another"
}
]
},
"number": 1.4567,
"bool": true
}`,
map[string]interface{}{
"flag-foo_jim": "bean",
"flag-fee": "bar",
"flag-n1_alist_0": "a",
"flag-n1_alist_1": "b",
"flag-n1_alist_2": "c",
"flag-n1_alist_3_d": "other",
"flag-n1_alist_3_e": "another",
"flag-number": 1.4567,
"flag-bool": true,
},
"flag-",
UnderscoreStyle,
},
}
for i, test := range cases {
var m interface{}
err := json.Unmarshal([]byte(test.test), &m)
if err != nil {
t.Errorf("%d: failed to unmarshal test: %v", i+1, err)
continue
}
got, err := Flatten(m.(map[string]interface{}), test.prefix, test.style)
if err != nil {
t.Errorf("%d: failed to flatten: %v", i+1, err)
continue
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("%d: mismatch, got: %v wanted: %v", i+1, got, test.want)
}
}
}
func TestFlattenString(t *testing.T) {
cases := []struct {
test string
want string
prefix string
style SeparatorStyle
err error
}{
// 1
{
`{ "a": "b" }`,
`{ "a": "b" }`,
"",
DotStyle,
nil,
},
// 2
{
`{ "a": { "b" : { "c" : { "d" : "e" } } }, "number": 1.4567, "bool": true }`,
`{ "a.b.c.d": "e", "bool": true, "number": 1.4567 }`,
"",
DotStyle,
nil,
},
// 3
{
`{ "a": { "b" : { "c" : { "d" : "e" } } }, "number": 1.4567, "bool": true }`,
`{ "a/b/c/d": "e", "bool": true, "number": 1.4567 }`,
"",
PathStyle,
nil,
},
// 4
{
`{ "a": { "b" : { "c" : { "d" : "e" } } } }`,
`{ "a--b--c--d": "e" }`,
"",
SeparatorStyle{Middle: "--"}, // emdash
nil,
},
// 5
{
`{ "a": { "b" : { "c" : { "d" : "e" } } } }`,
`{ "a(b)(c)(d)": "e" }`,
"",
SeparatorStyle{Before: "(", After: ")"}, // paren groupings
nil,
},
// 6 -- with leading whitespace
{
`
{ "a": { "b" : { "c" : { "d" : "e" } } } }`,
`{ "a(b)(c)(d)": "e" }`,
"",
SeparatorStyle{Before: "(", After: ")"}, // paren groupings
nil,
},
//
// Valid JSON text, but invalid for FlattenString
//
// 7
{
`[ "a": { "b": "c" }, "d" ]`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 8
{
``,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 9
{
`astring`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 10
{
`false`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 11
{
`42`,
`bogus`,
"",
PathStyle,
NotValidJsonInputError,
},
// 12 -- prior to version 1.0.1, this was accepted & unmarshalled as an empty map, finally returning `{}`.
{
`null`,
`{}`,
"",
PathStyle,
NotValidJsonInputError,
},
// 13 -- try a prefix
{
`{ "a": { "b" : { "c" : { "d" : "e" } } }, "number": 1.4567, "bool": true }`,
`{ "flag-a.b.c.d": "e", "flag-bool": true, "flag-number": 1.4567 }`,
"flag-",
DotStyle,
nil,
},
}
for i, test := range cases {
got, err := FlattenString(test.test, test.prefix, test.style)
if err != test.err {
t.Errorf("%d: error mismatch, got: [%v], wanted: [%v]", i+1, err, test.err)
continue
}
if err != nil {
continue
}
nixws := func(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}
if got != strings.Map(nixws, test.want) {
t.Errorf("%d: mismatch, got: %v wanted: %v", i+1, got, test.want)
}
}
}
================================================
FILE: v2/go.mod
================================================
module github.com/jeremywohl/flatten/v2
gitextract_fuvuek_y/
├── .gitignore
├── LICENSE
├── README.md
├── TODO
├── doc.go
├── flatten.go
├── flatten_test.go
├── go.mod
└── v2/
├── README.md
├── doc.go
├── flatten.go
├── flatten_test.go
└── go.mod
SYMBOL INDEX (14 symbols across 4 files)
FILE: flatten.go
type SeparatorStyle (line 18) | type SeparatorStyle struct
function Flatten (line 45) | func Flatten(nested map[string]interface{}, prefix string, style Separat...
function FlattenString (line 64) | func FlattenString(nestedstr, prefix string, style SeparatorStyle) (stri...
function flatten (line 88) | func flatten(top bool, flatMap map[string]interface{}, nested interface{...
function enkey (line 120) | func enkey(top bool, prefix, subkey string, style SeparatorStyle) string {
FILE: flatten_test.go
function TestFlatten (line 11) | func TestFlatten(t *testing.T) {
function TestFlattenString (line 219) | func TestFlattenString(t *testing.T) {
FILE: v2/flatten.go
type SeparatorStyle (line 18) | type SeparatorStyle struct
function Flatten (line 45) | func Flatten(nested map[string]interface{}, prefix string, style Separat...
function FlattenString (line 64) | func FlattenString(nestedstr, prefix string, style SeparatorStyle) (stri...
function flatten (line 88) | func flatten(top bool, flatMap map[string]interface{}, nested interface{...
function enkey (line 120) | func enkey(top bool, prefix, subkey string, style SeparatorStyle) string {
FILE: v2/flatten_test.go
function TestFlatten (line 11) | func TestFlatten(t *testing.T) {
function TestFlattenString (line 219) | func TestFlattenString(t *testing.T) {
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (33K chars).
[
{
"path": ".gitignore",
"chars": 266,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": "LICENSE",
"chars": 1078,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Jeremy Wohl\n\nPermission is hereby granted, free of charge, to any person obtai"
},
{
"path": "README.md",
"chars": 1541,
"preview": "flatten\n=======\n\n[](https://pkg.go.dev/github"
},
{
"path": "TODO",
"chars": 129,
"preview": "test: all Go scalar types\ntodo: initial list vs map\ntodo: fail properly with alternate types\ntodo: support structs and p"
},
{
"path": "doc.go",
"chars": 1388,
"preview": "// Flatten makes flat, one-dimensional maps from arbitrarily nested ones.\n//\n// It turns map keys into compound\n// names"
},
{
"path": "flatten.go",
"chars": 3542,
"preview": "package flatten\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"regexp\"\n\t\"strconv\"\n)\n\n// The style of keys. If there is an input"
},
{
"path": "flatten_test.go",
"chars": 6159,
"preview": "package flatten\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"unicode\"\n)\n\nfunc TestFlatten(t *testing.T)"
},
{
"path": "go.mod",
"chars": 37,
"preview": "module github.com/jeremywohl/flatten\n"
},
{
"path": "v2/README.md",
"chars": 1523,
"preview": "flatten\n=======\n\n[](https://pkg"
},
{
"path": "v2/doc.go",
"chars": 1388,
"preview": "// Flatten makes flat, one-dimensional maps from arbitrarily nested ones.\n//\n// It turns map keys into compound\n// names"
},
{
"path": "v2/flatten.go",
"chars": 3542,
"preview": "package flatten\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"regexp\"\n\t\"strconv\"\n)\n\n// The style of keys. If there is an input"
},
{
"path": "v2/flatten_test.go",
"chars": 6159,
"preview": "package flatten\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"unicode\"\n)\n\nfunc TestFlatten(t *testing.T)"
},
{
"path": "v2/go.mod",
"chars": 40,
"preview": "module github.com/jeremywohl/flatten/v2\n"
}
]
About this extraction
This page contains the full source code of the jeremywohl/flatten GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (26.2 KB), approximately 9.5k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.