pax_global_header00006660000000000000000000000064135577425340014531gustar00rootroot0000000000000052 comment=929980fd87f1df0d6323cdac61ea3844535f609d flatten-1.0.1/000077500000000000000000000000001355774253400131655ustar00rootroot00000000000000flatten-1.0.1/.gitignore000066400000000000000000000004121355774253400151520ustar00rootroot00000000000000# 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 flatten-1.0.1/.travis.yml000066400000000000000000000000151355774253400152720ustar00rootroot00000000000000language: go flatten-1.0.1/LICENSE000066400000000000000000000020661355774253400141760ustar00rootroot00000000000000The 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. flatten-1.0.1/README.md000066400000000000000000000031071355774253400144450ustar00rootroot00000000000000flatten ======= [![GoDoc](https://godoc.org/github.com/jeremywohl/flatten?status.png)](https://godoc.org/github.com/jeremywohl/flatten) [![Build Status](https://travis-ci.org/jeremywohl/flatten.svg?branch=master)](https://travis-ci.org/jeremywohl/flatten) 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. flatten-1.0.1/TODO000066400000000000000000000002011355774253400136460ustar00rootroot00000000000000test: all Go scalar types todo: initial list vs map todo: fail properly with alternate types todo: support structs and pointers? flatten-1.0.1/doc.go000066400000000000000000000025541355774253400142670ustar00rootroot00000000000000// 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 flatten-1.0.1/flatten.go000066400000000000000000000067261355774253400151640ustar00rootroot00000000000000package 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 } flatten-1.0.1/flatten_test.go000066400000000000000000000122331355774253400162110ustar00rootroot00000000000000package 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 }{ { `{ "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, }, { `{ "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, }, { `{ "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, }, { `{ "a": { "b": "c" }, "e": "f" }`, map[string]interface{}{ "p:a.b": "c", "p:e": "f", }, "p:", DotStyle, }, { `{ "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, }, } 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, }, } 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) } } }