├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── json.go ├── json_test.go ├── lodash.go ├── lodash_test.go ├── tabify ├── node_value.go ├── options.go ├── tabify.go ├── tabify_test.go ├── table_buffer.go ├── table_writer.go └── tests │ ├── histogram_terms_agg.json │ ├── histogram_terms_agg_expected.json │ ├── histogram_terms_agg_slice.json │ ├── metric_agg.json │ ├── metric_agg_expected.json │ ├── metric_agg_slice.json │ ├── nested_agg.json │ ├── nested_agg_expected.json │ ├── nested_agg_slice.json │ ├── terms_terms_sum_agg.json │ ├── terms_terms_sum_agg_expected.json │ ├── terms_terms_sum_agg_slice.json │ ├── triple_agg.json │ ├── triple_agg_expected.json │ └── triple_agg_slice.json ├── utils.go └── utils_test.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.14 6 | steps: 7 | - checkout 8 | - run: 9 | name: Tests 10 | command: | 11 | go fmt ./... 12 | go vet ./... 13 | go test -v ./... 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | 14 | [*.{js,jsx,json,html}] 15 | indent_size = 4 16 | 17 | [Makefile] 18 | indent_style = tab 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | bin/ 3 | data/ 4 | .DS_Store 5 | .env 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [] 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.testFlags": ["-v", "-count=1"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017-2020 Datasweet 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # jsonmap 3 | [![Circle CI](https://circleci.com/gh/datasweet/jsonmap.svg?style=svg)](https://circleci.com/gh/datasweet/jsonmap) [![Go Report Card](https://goreportcard.com/badge/github.com/datasweet/jsonmap)](https://goreportcard.com/report/github.com/datasweet/jsonmap) [![GoDoc](https://godoc.org/github.com/datasweet/jsonmap?status.png)](https://godoc.org/github.com/datasweet/jsonmap) [![GitHub stars](https://img.shields.io/github/stars/datasweet/jsonmap.svg)](https://github.com/datasweet/jsonmap/stargazers) 4 | [![GitHub license](https://img.shields.io/github/license/datasweet/jsonmap.svg)](https://github.com/datasweet/jsonmap/blob/master/LICENSE) 5 | 6 | [![datasweet-logo](https://www.datasweet.fr/wp-content/uploads/2019/02/datasweet-black.png)](http://www.datasweet.fr) 7 | 8 | Jsonmap is a Go package to parse and use raw JSON with silent as Javascript language. 9 | Jsonmap was inspired by [go-simplejson](https://github.com/bitly/go-simplejson) and the [lodash](https://lodash.com/) javascript library. 10 | 11 | 12 | ## Installation 13 | ``` 14 | go get github.com/datasweet/jsonmap 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Parsing a new json 20 | ``` 21 | import ( 22 | "fmt" 23 | "github.com/datasweet/jsonmap" 24 | ) 25 | 26 | func main() { 27 | j := jsonmap.FromString(`{ "the": { "best": { "pi": 3.14 } } }`) 28 | i := j.Get("the.best.pi").AsInt() 29 | fmt.Println(i) 30 | } 31 | ``` 32 | 33 | ### Lodash utilities 34 | You can use some lodash function utilities : 35 | * Filter 36 | * Map 37 | * Assign 38 | * etc. 39 | 40 | ### Tabify 41 | Tabify was created to flatten a json into tabular datas. We created this functionality to flatten json response from elasticsearch. 42 | 43 | ## Who are we ? 44 | We are Datasweet, a french startup providing full service (big) data solutions. 45 | 46 | ## Questions ? problems ? suggestions ? 47 | If you find a bug or want to request a feature, please create a [GitHub Issue](https://github.com/datasweet/jsonmap/issues/new). 48 | 49 | ## License 50 | ``` 51 | This software is licensed under the Apache License, version 2 ("ALv2"), quoted below. 52 | 53 | Copyright 2017-2020 Datasweet 54 | 55 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 56 | use this file except in compliance with the License. You may obtain a copy of 57 | the License at 58 | 59 | http://www.apache.org/licenses/LICENSE-2.0 60 | 61 | Unless required by applicable law or agreed to in writing, software 62 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 63 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 64 | License for the specific language governing permissions and limitations under 65 | the License. 66 | ``` 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/datasweet/jsonmap 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/stretchr/testify v1.6.1 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 9 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | package jsonmap 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | // Json is our wrapper to an unmarshalled json 11 | type Json struct { 12 | data interface{} 13 | } 14 | 15 | // A Jsonizer can converts to a json 16 | type Jsonizer interface { 17 | JSON() *Json 18 | } 19 | 20 | func jsonize(j Jsonizer) *Json { 21 | if j == nil { 22 | return Nil() 23 | } 24 | if rv := reflect.ValueOf(j); rv.IsNil() { 25 | return Nil() 26 | } 27 | return j.JSON() 28 | } 29 | 30 | // New creates an empty object Json, ie {} 31 | func New() *Json { 32 | return &Json{ 33 | data: make(map[string]interface{}), 34 | } 35 | } 36 | 37 | // Nil creates an nil Json 38 | func Nil() *Json { 39 | return &Json{nil} 40 | } 41 | 42 | // FromBytes to creates a Json from bytes 43 | func FromBytes(bytes []byte) *Json { 44 | j := new(Json) 45 | j.UnmarshalJSON(bytes) 46 | return j 47 | } 48 | 49 | // FromString to creates a Json from a string 50 | func FromString(str string) *Json { 51 | return FromBytes([]byte(str)) 52 | } 53 | 54 | // FromMap to creates a Json from an unmarshalled map 55 | func FromMap(m map[string]interface{}) *Json { 56 | return &Json{ 57 | data: m, 58 | } 59 | } 60 | 61 | // Stringify formats current node to a json string 62 | func (j *Json) Stringify() string { 63 | return string(j.Bytes()) 64 | } 65 | 66 | // Bytes return json bytes 67 | func (j *Json) Bytes() []byte { 68 | bytes, _ := j.MarshalJSON() 69 | return bytes 70 | } 71 | 72 | // MarshalJSON implements marshaler interface from encoding/json encode.go 73 | func (j *Json) MarshalJSON() ([]byte, error) { 74 | if j.IsNil() { 75 | return []byte("null"), nil 76 | } 77 | bytes, err := json.Marshal(j.data) 78 | if err != nil { 79 | return []byte("null"), err 80 | } 81 | return bytes, nil 82 | } 83 | 84 | // UnmarshalJSON implements unmarshaler interface from encoding/json decode.go 85 | func (j *Json) UnmarshalJSON(data []byte) error { 86 | if j == nil { 87 | return errors.New("unmarshal JSON on a nil pointer") 88 | } 89 | return json.Unmarshal(data, &j.data) 90 | } 91 | 92 | // Data get uncasted data 93 | func (j *Json) Data() interface{} { 94 | return j.data 95 | } 96 | 97 | // IsNil to check if the current Json is nil 98 | func (j *Json) IsNil() bool { 99 | return j.data == nil 100 | } 101 | 102 | // IsObject to know if the current Json is an object 103 | func (j *Json) IsObject() bool { 104 | _, ok := (j.data).(map[string]interface{}) 105 | return ok 106 | } 107 | 108 | // AsObject casts underlying to object (map[string]interface{}) 109 | // Returns nil if not an object 110 | func (j *Json) AsObject() map[string]interface{} { 111 | if casted, ok := (j.data).(map[string]interface{}); ok { 112 | return casted 113 | } 114 | return nil 115 | } 116 | 117 | // IsArray to check if the current Json is an array 118 | func (j *Json) IsArray() bool { 119 | _, ok := (j.data).([]interface{}) 120 | return ok 121 | } 122 | 123 | // AsArray casts underlying to array ([]interface{}) 124 | // Returns nil if not an array 125 | func (j *Json) AsArray() []interface{} { 126 | if casted, ok := (j.data).([]interface{}); ok { 127 | return casted 128 | } 129 | return nil 130 | } 131 | 132 | // IsValue to check if the current Json is a type value 133 | func (j *Json) IsValue() bool { 134 | return !j.IsNil() && !j.IsObject() && !j.IsArray() 135 | } 136 | 137 | // AsString casts underlying to string 138 | // Returns an empty string if not a string 139 | func (j *Json) AsString() string { 140 | if casted, ok := (j.data).(string); ok { 141 | return casted 142 | } 143 | return "" 144 | } 145 | 146 | // AsBool casts underlying to boolean 147 | // Returns false if not a boolean 148 | func (j *Json) AsBool() bool { 149 | if casted, ok := (j.data).(bool); ok { 150 | return casted 151 | } 152 | return false 153 | } 154 | 155 | // AsInt casts underlying to int64 156 | // Returns 0 if not an int 157 | func (j *Json) AsInt() int64 { 158 | switch j.data.(type) { 159 | case json.Number: 160 | if i, err := (j.data).(json.Number).Int64(); err != nil { 161 | return i 162 | } 163 | return 0 164 | case float32, float64: 165 | return int64(reflect.ValueOf(j.data).Float()) 166 | case int, int8, int16, int32, int64: 167 | return reflect.ValueOf(j.data).Int() 168 | case uint, uint8, uint16, uint32, uint64: 169 | return int64(reflect.ValueOf(j.data).Uint()) 170 | default: 171 | return 0 172 | } 173 | } 174 | 175 | // AsUint casts underlying to uint64 176 | // Returns 0 if not an int 177 | func (j *Json) AsUint() uint64 { 178 | switch j.data.(type) { 179 | case json.Number: 180 | if u, err := strconv.ParseUint(j.data.(json.Number).String(), 10, 64); err != nil { 181 | return u 182 | } 183 | return 0 184 | case float32, float64: 185 | return uint64(reflect.ValueOf(j.data).Float()) 186 | case int, int8, int16, int32, int64: 187 | return uint64(reflect.ValueOf(j.data).Int()) 188 | case uint, uint8, uint16, uint32, uint64: 189 | return reflect.ValueOf(j.data).Uint() 190 | default: 191 | return 0 192 | } 193 | } 194 | 195 | // AsFloat casts underlying to float64 196 | // Returns 0 if not a float 197 | func (j *Json) AsFloat() float64 { 198 | switch j.data.(type) { 199 | case json.Number: 200 | if f, err := (j.data).(json.Number).Float64(); err != nil { 201 | return f 202 | } 203 | return 0 204 | case float32, float64: 205 | return reflect.ValueOf(j.data).Float() 206 | case int, int8, int16, int32, int64: 207 | return float64(reflect.ValueOf(j.data).Int()) 208 | case uint, uint8, uint16, uint32, uint64: 209 | return float64(reflect.ValueOf(j.data).Uint()) 210 | default: 211 | return 0 212 | } 213 | } 214 | 215 | // Get gets the value at path of object. If not found returns Nils() value 216 | func (j *Json) Get(path string) *Json { 217 | keys := createPath(path) 218 | curr := j 219 | for _, k := range keys { 220 | 221 | // Get as object 222 | if o := curr.AsObject(); o != nil { 223 | val, ok := o[k] 224 | if !ok { 225 | return Nil() 226 | } 227 | curr = &Json{val} 228 | continue 229 | } 230 | 231 | // Get as array 232 | if a := curr.AsArray(); a != nil { 233 | // Must be an int 234 | idx, e := strconv.Atoi(k) 235 | if e != nil || idx < 0 || idx >= len(a) { 236 | return Nil() 237 | } 238 | curr = &Json{a[idx]} 239 | continue 240 | } 241 | 242 | // Not found 243 | return Nil() 244 | } 245 | 246 | return curr 247 | } 248 | 249 | // Has checks if path is a direct property of object. 250 | func (j *Json) Has(path string) bool { 251 | o := j.Get(path) 252 | return !o.IsNil() 253 | } 254 | 255 | // Set sets the value at path of object. If a portion of path doesn't exist, it's created. 256 | // Arrays are created for missing index properties while objects are created for all other missing properties 257 | func (j *Json) Set(path string, value interface{}) bool { 258 | keys := createPath(path) 259 | lastIndex := len(keys) - 1 260 | 261 | // Pick value 262 | var newValue interface{} 263 | 264 | switch cv := value.(type) { 265 | case Jsonizer: 266 | newValue = jsonize(cv).data 267 | 268 | case *Json: 269 | newValue = cv.data 270 | 271 | case []*Json: 272 | datas := make([]interface{}, 0, len(cv)) 273 | for _, item := range cv { 274 | datas = append(datas, item.data) 275 | } 276 | newValue = datas 277 | 278 | case []interface{}: 279 | newValue = cv 280 | 281 | default: 282 | rv := reflect.ValueOf(value) 283 | kind := rv.Kind() 284 | 285 | if kind == reflect.Array || kind == reflect.Slice { 286 | l := rv.Len() 287 | datas := make([]interface{}, l) 288 | for i := 0; i < l; i++ { 289 | val := rv.Index(i) 290 | if !val.CanInterface() { 291 | continue 292 | } 293 | 294 | if jsonizer, ok := val.Interface().(Jsonizer); ok { 295 | datas[i] = jsonize(jsonizer).data 296 | continue 297 | } 298 | 299 | datas[i] = val.Interface() 300 | } 301 | newValue = datas 302 | } else { 303 | newValue = value 304 | } 305 | } 306 | 307 | if lastIndex == -1 { 308 | j.data = newValue 309 | return true 310 | } 311 | 312 | curr := j 313 | for i, k := range keys { 314 | // Get as object 315 | if o := curr.AsObject(); o != nil { 316 | // Assign value 317 | if i == lastIndex { 318 | o[k] = newValue 319 | return true 320 | } 321 | 322 | if _, ok := o[k]; !ok { 323 | o[k] = make(map[string]interface{}) 324 | } 325 | curr = &Json{o[k]} 326 | continue 327 | } 328 | 329 | // Get as array 330 | if a := curr.AsArray(); a != nil { 331 | // Must be an int 332 | idx, e := strconv.Atoi(k) 333 | if e != nil || idx < 0 || idx >= len(a) { 334 | return false 335 | } 336 | 337 | // Assign value 338 | if i == lastIndex { 339 | a[idx] = newValue 340 | return true 341 | } 342 | 343 | curr = &Json{a[idx]} 344 | continue 345 | } 346 | 347 | // Value or nil => we force the rewrite 348 | m := make(map[string]interface{}) 349 | curr.data = m 350 | 351 | // Assign 352 | if i == lastIndex { 353 | m[k] = newValue 354 | return true 355 | } 356 | 357 | m[k] = make(map[string]interface{}) 358 | curr = &Json{m[k]} 359 | } 360 | 361 | return false 362 | } 363 | 364 | // Unset deletes the value 365 | func (j *Json) Unset(path string) bool { 366 | keys := createPath(path) 367 | curr := j 368 | lastIndex := len(keys) - 1 369 | 370 | for i, k := range keys { 371 | // Get as object 372 | if o := curr.AsObject(); o != nil { 373 | if i == lastIndex { 374 | delete(o, k) 375 | return true 376 | } 377 | val, ok := o[k] 378 | if !ok { 379 | return false 380 | } 381 | curr = &Json{val} 382 | continue 383 | } 384 | 385 | // Get as array 386 | if a := curr.AsArray(); a != nil { 387 | // Must be an int 388 | idx, e := strconv.Atoi(k) 389 | if e != nil || idx < 0 || idx >= len(a) { 390 | return false 391 | } 392 | if i == lastIndex { 393 | a[idx] = nil 394 | return true 395 | } 396 | curr = &Json{a[idx]} 397 | continue 398 | } 399 | 400 | // Not found 401 | return false 402 | } 403 | return false 404 | } 405 | 406 | // Rewrite changes a path 407 | func (j *Json) Rewrite(oldPath string, newPath string) bool { 408 | d := j.Get(oldPath).Data() 409 | return j.Unset(oldPath) && j.Set(newPath, d) 410 | } 411 | 412 | // Wrap the current json to a new json 413 | // Example : { "pi": 3.14 }.Wrap("const") => { "const": { "pi": 3.14 }} } 414 | // Returns the new parent or nilJson if error 415 | func (j *Json) Wrap(path string) *Json { 416 | wrap := New() 417 | if !wrap.Set(path, j) { 418 | return Nil() 419 | } 420 | return wrap 421 | } 422 | 423 | // ForEach : Iterates over elements of collection and invokes iteratee for each element. 424 | // Iteratee functions may exit iteration early by explicitly returning false. 425 | func (j *Json) ForEach(iteratee func(k string, v *Json) bool) { 426 | if iteratee == nil { 427 | return 428 | } 429 | 430 | if o := j.AsObject(); o != nil { 431 | for k, v := range o { 432 | if !iteratee(k, &Json{v}) { 433 | break 434 | } 435 | } 436 | } else if a := j.AsArray(); a != nil { 437 | for i, v := range a { 438 | if !iteratee(strconv.Itoa(i), &Json{v}) { 439 | break 440 | } 441 | } 442 | } 443 | } 444 | 445 | // Keys : creates an array of the own property names of object. 446 | func (j *Json) Keys() []string { 447 | var keys []string 448 | 449 | callback := func(k string, v *Json) bool { 450 | keys = append(keys, k) 451 | return true 452 | } 453 | 454 | j.ForEach(callback) 455 | return keys 456 | } 457 | 458 | // Values : creates an array of the own enumerable string keyed property values of object. 459 | func (j *Json) Values() []*Json { 460 | var values []*Json 461 | 462 | callback := func(k string, v *Json) bool { 463 | values = append(values, v) 464 | return true 465 | } 466 | 467 | j.ForEach(callback) 468 | 469 | return values 470 | } 471 | 472 | // Clone to clone a json 473 | // Not the best performance cause we marshal / unmarshal 474 | func (j *Json) Clone() *Json { 475 | bytes, err := j.MarshalJSON() 476 | if err != nil { 477 | return Nil() 478 | } 479 | return FromBytes(bytes) 480 | } 481 | 482 | // Merge to merge multiples JSON into a single one 483 | func Merge(jsons ...*Json) *Json { 484 | res := New() 485 | m := make(map[string]interface{}) 486 | for _, j := range jsons { 487 | for _, k := range j.Keys() { 488 | m[k] = j.AsObject()[k] 489 | } 490 | } 491 | res.data = m 492 | return res 493 | } 494 | -------------------------------------------------------------------------------- /json_test.go: -------------------------------------------------------------------------------- 1 | package jsonmap_test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "sort" 7 | "testing" 8 | 9 | "github.com/datasweet/jsonmap" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | const jsonTest = ` 14 | { 15 | "string": "hello", 16 | "bool": true, 17 | "number": 123, 18 | "array": [1,2,3,4,5], 19 | "object": { 20 | "test": "world", 21 | "sub": [ 22 | {"a": 4, "1": "a" }, 23 | {"a": 5, "1": "b" } 24 | ] 25 | } 26 | } 27 | ` 28 | 29 | type Person struct { 30 | FirstName string 31 | Name string 32 | Age int 33 | } 34 | 35 | func (p *Person) JSON() *jsonmap.Json { 36 | j := jsonmap.New() 37 | j.Set("name", fmt.Sprintf("%s %s", p.FirstName, p.Name)) 38 | j.Set("age", p.Age) 39 | return j 40 | } 41 | 42 | func TestNewJson(t *testing.T) { 43 | j := jsonmap.New() 44 | assert.False(t, j.IsNil()) 45 | assert.Equal(t, "{}", j.Stringify()) 46 | } 47 | 48 | func TestFromWrongString(t *testing.T) { 49 | j := jsonmap.FromString("hello") 50 | assert.True(t, j.IsNil()) 51 | assert.Equal(t, "null", j.Stringify()) 52 | } 53 | 54 | func TestFromString(t *testing.T) { 55 | j := jsonmap.FromString(jsonTest) 56 | assert.False(t, j.IsNil()) 57 | assert.False(t, j.IsValue()) 58 | assert.False(t, j.IsArray()) 59 | assert.True(t, j.IsObject()) 60 | } 61 | 62 | func TestAsString(t *testing.T) { 63 | j := jsonmap.FromString(jsonTest) 64 | assert.Equal(t, "hello", j.Get("string").AsString()) 65 | assert.Equal(t, "", j.Get("bool").AsString()) 66 | assert.Equal(t, "", j.Get("number").AsString()) 67 | assert.Equal(t, "", j.Get("array").AsString()) 68 | assert.Equal(t, "", j.Get("object").AsString()) 69 | assert.Equal(t, "", j.Get("unknown").AsString()) 70 | } 71 | 72 | func TestAsBool(t *testing.T) { 73 | j := jsonmap.FromString(jsonTest) 74 | assert.False(t, j.Get("string").AsBool()) 75 | assert.True(t, j.Get("bool").AsBool()) 76 | assert.False(t, j.Get("number").AsBool()) 77 | assert.False(t, j.Get("array").AsBool()) 78 | assert.False(t, j.Get("object").AsBool()) 79 | assert.False(t, j.Get("unknown").AsBool()) 80 | } 81 | 82 | func TestAsInt(t *testing.T) { 83 | j := jsonmap.FromString(jsonTest) 84 | assert.Equal(t, int64(0), j.Get("string").AsInt()) 85 | assert.Equal(t, int64(0), j.Get("bool").AsInt()) 86 | assert.Equal(t, int64(123), j.Get("number").AsInt()) 87 | assert.Equal(t, int64(0), j.Get("array").AsInt()) 88 | assert.Equal(t, int64(0), j.Get("object").AsInt()) 89 | assert.Equal(t, int64(0), j.Get("unknown").AsInt()) 90 | } 91 | 92 | func TestAsUint(t *testing.T) { 93 | j := jsonmap.FromString(jsonTest) 94 | assert.Equal(t, uint64(0), j.Get("string").AsUint()) 95 | assert.Equal(t, uint64(0), j.Get("bool").AsUint()) 96 | assert.Equal(t, uint64(123), j.Get("number").AsUint()) 97 | assert.Equal(t, uint64(0), j.Get("array").AsUint()) 98 | assert.Equal(t, uint64(0), j.Get("object").AsUint()) 99 | assert.Equal(t, uint64(0), j.Get("unknown").AsUint()) 100 | } 101 | 102 | func TestAsFloat64(t *testing.T) { 103 | j := jsonmap.FromString(jsonTest) 104 | assert.Equal(t, float64(0), j.Get("string").AsFloat()) 105 | assert.Equal(t, float64(0), j.Get("bool").AsFloat()) 106 | assert.Equal(t, float64(123), j.Get("number").AsFloat()) 107 | assert.Equal(t, float64(0), j.Get("array").AsFloat()) 108 | assert.Equal(t, float64(0), j.Get("object").AsFloat()) 109 | assert.Equal(t, float64(0), j.Get("unknown").AsFloat()) 110 | } 111 | 112 | func TestObjectKeys(t *testing.T) { 113 | j := jsonmap.FromString(jsonTest) 114 | 115 | expected := []string{"string", "bool", "number", "array", "object"} 116 | sort.Strings(expected) 117 | 118 | actual := j.Keys() 119 | sort.Strings(actual) 120 | 121 | assert.EqualValues(t, expected, actual) 122 | } 123 | 124 | func TestGetPath(t *testing.T) { 125 | j := jsonmap.FromString(jsonTest) 126 | assert.Equal(t, "a", j.Get("object.sub[0].1").AsString()) 127 | assert.Equal(t, "b", j.Get("object.sub[1].1").AsString()) 128 | assert.True(t, j.Get("object.test.sub2").IsNil()) 129 | assert.True(t, j.Get("object.test.sub[2].1").IsNil()) 130 | } 131 | 132 | func TestSet(t *testing.T) { 133 | t.Run("can set root path", func(t *testing.T) { 134 | j := jsonmap.New() 135 | assert.True(t, j.Set("", 3.14)) 136 | assert.JSONEq(t, "3.14", j.Stringify()) 137 | assert.Equal(t, 3.14, j.AsFloat()) 138 | }) 139 | 140 | t.Run("can replace a value by an object", func(t *testing.T) { 141 | j := jsonmap.New() 142 | assert.True(t, j.Set("", 3.14)) 143 | assert.True(t, j.Set("hello", "world")) 144 | assert.JSONEq(t, `{ "hello": "world" }`, j.Stringify()) 145 | }) 146 | 147 | t.Run("can replace an object by a value", func(t *testing.T) { 148 | j := jsonmap.New() 149 | assert.True(t, j.Set("hello", "world")) 150 | assert.True(t, j.Set("", 3.14)) 151 | assert.JSONEq(t, "3.14", j.Stringify()) 152 | }) 153 | 154 | t.Run("can create sub path", func(t *testing.T) { 155 | j := jsonmap.New() 156 | assert.True(t, j.Set("hello", "world")) 157 | assert.True(t, j.Set("the.number.pi.is", 3.14)) 158 | assert.JSONEq(t, `{ "hello": "world", "the": { "number": { "pi": { "is": 3.14 }}}}`, j.Stringify()) 159 | }) 160 | 161 | t.Run("can create path with escape '.'", func(t *testing.T) { 162 | j := jsonmap.New() 163 | assert.True(t, j.Set("test\\.machin", 45)) 164 | assert.True(t, j.Set("choux\\.machin.truc", "bidule")) 165 | assert.True(t, j.Set("choux.machin\\.truc", "bidule")) 166 | assert.JSONEq(t, `{ "test.machin": 45, "choux.machin": { "truc": "bidule" }, "choux": { "machin.truc": "bidule" }}`, j.Stringify()) 167 | }) 168 | 169 | t.Run("can set array", func(t *testing.T) { 170 | j := jsonmap.New() 171 | assert.True(t, j.Set("items", []int{1, 2, 3, 4, 5})) 172 | assert.JSONEq(t, `{ "items": [1, 2, 3, 4, 5] }`, j.Stringify()) 173 | 174 | assert.True(t, j.Set("items[2]", 3.14)) 175 | assert.JSONEq(t, `{ "items": [1, 2, 3.14, 4, 5] }`, j.Stringify()) 176 | 177 | assert.True(t, j.Set("items[3]", &Person{FirstName: "Thomas", Name: "CHARLOT", Age: 36})) 178 | assert.JSONEq(t, `{ "items": [1, 2, 3.14, {"name": "Thomas CHARLOT", "age": 36 }, 5] }`, j.Stringify()) 179 | 180 | assert.True(t, j.Set("items[3].age", 37)) 181 | assert.JSONEq(t, `{ "items": [1, 2, 3.14, {"name": "Thomas CHARLOT", "age": 37 }, 5] }`, j.Stringify()) 182 | 183 | assert.False(t, j.Set("items[7]", 11)) 184 | assert.JSONEq(t, `{ "items": [1, 2, 3.14, {"name": "Thomas CHARLOT", "age": 37 }, 5] }`, j.Stringify()) 185 | }) 186 | 187 | t.Run("can set nil", func(t *testing.T) { 188 | j := jsonmap.New() 189 | assert.True(t, j.Set("nil", nil)) 190 | assert.JSONEq(t, `{ "nil": null }`, j.Stringify()) 191 | }) 192 | 193 | t.Run("can set a sub json", func(t *testing.T) { 194 | j := jsonmap.New() 195 | sub := jsonmap.New() 196 | assert.True(t, sub.Set("pi", 3.14)) 197 | assert.True(t, j.Set("wrapped", sub)) 198 | assert.JSONEq(t, `{ "wrapped": { "pi": 3.14 }}`, j.Stringify()) 199 | }) 200 | 201 | t.Run("can set a json array", func(t *testing.T) { 202 | j := jsonmap.New() 203 | items := []*jsonmap.Json{ 204 | jsonmap.FromString(`{ "string": "hello" }`), 205 | jsonmap.FromString(`{ "bool": true }`), 206 | jsonmap.FromString(`{ "number": 3.14 }`), 207 | jsonmap.FromString(`{ "array": [1,2,3,4,5] }`), 208 | jsonmap.FromString(`{ "object": { "a": 4, "1": "a" }}`), 209 | jsonmap.Nil(), 210 | } 211 | 212 | assert.True(t, j.Set("items", items)) 213 | assert.JSONEq(t, 214 | `{ "items": [{ "string": "hello" }, { "bool": true }, { "number": 3.14 }, { "array": [1,2,3,4,5] }, { "object": { "a": 4, "1": "a" }}, null ]}`, 215 | j.Stringify(), 216 | ) 217 | }) 218 | 219 | t.Run("can set a jsonizer", func(t *testing.T) { 220 | j := jsonmap.New() 221 | person := &Person{FirstName: "Thomas", Name: "CHARLOT", Age: 36} 222 | assert.True(t, j.Set("person", person)) 223 | assert.JSONEq(t, 224 | `{ "person": { "name": "Thomas CHARLOT", "age": 36 }}`, 225 | j.Stringify(), 226 | ) 227 | }) 228 | 229 | t.Run("can set a nil jsonizer", func(t *testing.T) { 230 | j := jsonmap.New() 231 | var person *Person 232 | assert.Nil(t, person) 233 | assert.True(t, j.Set("person", person)) 234 | assert.JSONEq(t, 235 | `{ "person": null }`, 236 | j.Stringify(), 237 | ) 238 | }) 239 | 240 | t.Run("can set an array of jsonizer", func(t *testing.T) { 241 | j := jsonmap.New() 242 | peoples := []*Person{ 243 | &Person{FirstName: "Thomas", Name: "CHARLOT", Age: 36}, 244 | nil, 245 | &Person{FirstName: "Lionel", Name: "FROMENT", Age: 46}, 246 | } 247 | assert.True(t, j.Set("peoples", peoples)) 248 | assert.JSONEq(t, 249 | `{ "peoples": [{ "name": "Thomas CHARLOT", "age": 36 }, null, { "name": "Lionel FROMENT", "age": 46 }]}`, 250 | j.Stringify(), 251 | ) 252 | }) 253 | } 254 | 255 | func TestWrap(t *testing.T) { 256 | j := jsonmap.New() 257 | assert.True(t, j.Set("pi", 3.14)) 258 | 259 | wrap := j.Wrap("the.best") 260 | assert.False(t, j.IsNil()) 261 | assert.JSONEq(t, `{ "the": { "best": { "pi": 3.14 } } }`, wrap.Stringify()) 262 | 263 | } 264 | 265 | func TestForEachArray(t *testing.T) { 266 | j := jsonmap.FromString("[1, 2]") 267 | j.ForEach(func(k string, v *jsonmap.Json) bool { 268 | 269 | switch k { 270 | case "0": 271 | assert.Equal(t, int64(1), v.AsInt()) 272 | case "1": 273 | assert.Equal(t, int64(2), v.AsInt()) 274 | default: 275 | assert.Fail(t, "unknown key '%s'", k) 276 | 277 | } 278 | return true 279 | }) 280 | } 281 | 282 | func TestForEachObject(t *testing.T) { 283 | j := jsonmap.FromString(`{ "a": 1, "b": 2 }`) 284 | j.ForEach(func(k string, v *jsonmap.Json) bool { 285 | switch k { 286 | case "a": 287 | assert.Equal(t, int64(1), v.AsInt()) 288 | case "b": 289 | assert.Equal(t, int64(2), v.AsInt()) 290 | default: 291 | assert.Fail(t, "unknown key '%s'", k) 292 | 293 | } 294 | return true 295 | }) 296 | } 297 | 298 | func TestKeys(t *testing.T) { 299 | keys := jsonmap.FromString("[1, 2]").Keys() 300 | assert.Equal(t, 2, len(keys)) 301 | assert.Subset(t, []string{"0", "1"}, keys) 302 | 303 | keys = jsonmap.FromString(`{ "a": 1, "b": 2 }`).Keys() 304 | assert.Equal(t, 2, len(keys)) 305 | assert.Subset(t, []string{"a", "b"}, keys) 306 | } 307 | 308 | func TestValues(t *testing.T) { 309 | values := jsonmap.FromString("[1, 2]").Values() 310 | assert.Equal(t, 2, len(values)) 311 | var d1 []int64 312 | for _, v := range values { 313 | d1 = append(d1, v.AsInt()) 314 | } 315 | assert.Subset(t, []int64{1, 2}, d1) 316 | 317 | values = jsonmap.FromString(`{ "a": 1, "b": 2 }`).Values() 318 | assert.Equal(t, 2, len(values)) 319 | var d2 []int64 320 | for _, v := range values { 321 | d2 = append(d2, v.AsInt()) 322 | } 323 | assert.Subset(t, []int64{1, 2}, d2) 324 | } 325 | 326 | func TestUnset(t *testing.T) { 327 | j := jsonmap.FromString(jsonTest) 328 | assert.False(t, j.Get("object.sub[0].a").IsNil()) 329 | assert.True(t, j.Unset("object.sub[0].a")) 330 | assert.True(t, j.Get("object.sub[0].a").IsNil()) 331 | 332 | assert.False(t, j.Get("object.sub[1]").IsNil()) 333 | assert.True(t, j.Unset("object.sub[1]")) 334 | assert.True(t, j.Get("object.sub[1]").IsNil()) 335 | } 336 | 337 | func TestRewrite(t *testing.T) { 338 | j := jsonmap.FromString(jsonTest) 339 | assert.False(t, j.Get("object.sub[0].a").IsNil()) 340 | assert.True(t, j.Rewrite("object.sub[0].a", "new")) 341 | assert.True(t, j.Get("object.sub[0].a").IsNil()) 342 | assert.False(t, j.Get("new").IsNil()) 343 | } 344 | 345 | type Dummy struct { 346 | Raw *jsonmap.Json `json:"raw"` 347 | } 348 | 349 | func TestEncodeDecode(t *testing.T) { 350 | v := &Dummy{ 351 | Raw: jsonmap.FromString(jsonTest), 352 | } 353 | 354 | bytes, err := json.Marshal(v) 355 | assert.NoError(t, err) 356 | assert.NotNil(t, bytes) 357 | 358 | // // encode 359 | // buf := &bytes.Buffer{} 360 | // enc := json.NewEncoder(buf) 361 | // enc.SetEscapeHTML(true) data, err := json.Marshal(v) 362 | // assert.NoError(t, enc.Encode(v)) 363 | // assert.JSONEq(t, FromString(jsonTest).Wrap("raw").Stringify(), buf.String()) 364 | 365 | var dummy Dummy 366 | assert.NoError(t, json.Unmarshal(bytes, &dummy)) 367 | assert.JSONEq(t, jsonmap.FromString(jsonTest).Stringify(), dummy.Raw.Stringify()) 368 | } 369 | 370 | func TestClone(t *testing.T) { 371 | j := jsonmap.FromString(jsonTest) 372 | 373 | clone := j.Clone() 374 | 375 | assert.False(t, jsonmap.IsNil(clone)) 376 | 377 | j.Unset("string") 378 | assert.Equal(t, "", j.Get("string").AsString()) 379 | 380 | clone.Set("test", 12345) 381 | 382 | assert.Equal(t, "hello", clone.Get("string").AsString()) 383 | assert.Equal(t, true, clone.Get("bool").AsBool()) 384 | assert.Equal(t, float64(123), clone.Get("number").AsFloat()) 385 | assert.Equal(t, int64(4), clone.Get("object.sub[0].a").AsInt()) 386 | assert.Equal(t, int64(12345), clone.Get("test").AsInt()) 387 | assert.Equal(t, int64(0), j.Get("test").AsInt()) 388 | } 389 | 390 | func TestMerge(t *testing.T) { 391 | j := jsonmap.FromString(jsonTest) 392 | j2 := jsonmap.FromString(`{ 393 | "new": "value", 394 | "name": "john" 395 | }`) 396 | 397 | merge := jsonmap.Merge(j, j2) 398 | assert.NotNil(t, merge) 399 | assert.Equal(t, "hello", merge.Get("string").AsString()) 400 | assert.Equal(t, true, merge.Get("bool").AsBool()) 401 | assert.Equal(t, float64(123), merge.Get("number").AsFloat()) 402 | assert.Equal(t, int64(4), merge.Get("object.sub[0].a").AsInt()) 403 | assert.Equal(t, "value", merge.Get("new").AsString()) 404 | assert.Equal(t, "john", merge.Get("name").AsString()) 405 | assert.Len(t, merge.Keys(), 7) 406 | } 407 | -------------------------------------------------------------------------------- /lodash.go: -------------------------------------------------------------------------------- 1 | package jsonmap 2 | 3 | // IsNil checks if value is null or undefined. 4 | func IsNil(json *Json) bool { 5 | return json == nil || json.IsNil() 6 | } 7 | 8 | // Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for. 9 | func Filter(collection []*Json, predicate func(v *Json) bool) []*Json { 10 | var values []*Json 11 | 12 | if predicate == nil { 13 | return values 14 | } 15 | 16 | for _, j := range collection { 17 | if predicate(j) { 18 | copy := *j // filter must returns new array 19 | values = append(values, ©) 20 | } 21 | } 22 | 23 | return values 24 | } 25 | 26 | // Map creates an array of values by running each element in collection thru iteratee 27 | func Map(collection []*Json, iteratee func(v *Json) *Json) []*Json { 28 | var values []*Json 29 | 30 | if iteratee == nil { 31 | return values 32 | } 33 | 34 | for _, j := range collection { 35 | copy := *j // map must returns new array 36 | values = append(values, iteratee(©)) 37 | } 38 | 39 | return values 40 | } 41 | 42 | // Assign Assigns own enumerable string keyed properties of source objects to the destination object. 43 | func Assign(source *Json, json ...*Json) *Json { 44 | if IsNil(source) { 45 | source = New() 46 | } 47 | 48 | for _, j := range json { 49 | if !IsNil(j) { 50 | if o := j.AsObject(); o != nil { 51 | for k, v := range o { 52 | source.Set(k, v) 53 | } 54 | } 55 | } 56 | } 57 | 58 | return source 59 | } 60 | -------------------------------------------------------------------------------- /lodash_test.go: -------------------------------------------------------------------------------- 1 | package jsonmap_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/datasweet/jsonmap" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFilter(t *testing.T) { 12 | values := jsonmap.Filter(jsonmap.FromString("[1, 2, 3, 4, 5]").Values(), func(v *jsonmap.Json) bool { 13 | return v.AsInt()%2 == 1 14 | }) 15 | assert.Equal(t, 3, len(values)) 16 | for i, v := range values { 17 | assert.Equal(t, int64(2*i+1), v.AsInt()) 18 | } 19 | 20 | values = jsonmap.Filter(jsonmap.FromString(`[{ "value": 1 }, { "value": 2 }, { "value": 3 }, { "value": 4 }, { "value": 5 }]`).Values(), func(v *jsonmap.Json) bool { 21 | return v.Get("value").AsInt()%2 == 1 22 | }) 23 | assert.Equal(t, 3, len(values)) 24 | for i, v := range values { 25 | assert.JSONEq(t, fmt.Sprintf(`{ "value": %d }`, 2*i+1), v.Stringify()) 26 | } 27 | } 28 | 29 | func TestMap(t *testing.T) { 30 | values := jsonmap.Map(jsonmap.FromString("[1, 2, 3, 4, 5]").Values(), func(v *jsonmap.Json) *jsonmap.Json { 31 | if v.AsInt()%2 == 1 { 32 | return v 33 | } 34 | return jsonmap.Nil() 35 | }) 36 | assert.Equal(t, 5, len(values)) 37 | for i, v := range values { 38 | if i%2 == 0 { 39 | assert.Equal(t, int64(i+1), v.AsInt()) 40 | } else { 41 | assert.True(t, v.IsNil()) 42 | } 43 | } 44 | } 45 | 46 | func TestAssign(t *testing.T) { 47 | json1 := jsonmap.FromString(`{ 48 | "string": "hello", 49 | "bool": true, 50 | "number": 123 51 | }`) 52 | 53 | json2 := jsonmap.FromString(`{ 54 | "array": [1,2,3,4,5] 55 | }`) 56 | 57 | json3 := jsonmap.FromString(`{ 58 | "object": { 59 | "test": "world", 60 | "sub": [ 61 | {"a": 4, "1": "a" }, 62 | {"a": 5, "1": "b" } 63 | ] 64 | } 65 | }`) 66 | 67 | j := jsonmap.Assign(jsonmap.FromString(`{"hello": "world" }`), json1, json2, json3) 68 | 69 | expected := jsonmap.FromString(`{ 70 | "hello": "world", 71 | "string": "hello", 72 | "bool": true, 73 | "number": 123, 74 | "array": [1,2,3,4,5], 75 | "object": { 76 | "test": "world", 77 | "sub": [ 78 | {"a": 4, "1": "a" }, 79 | {"a": 5, "1": "b" } 80 | ] 81 | } 82 | }`) 83 | 84 | assert.JSONEq(t, expected.Stringify(), j.Stringify()) 85 | } 86 | -------------------------------------------------------------------------------- /tabify/node_value.go: -------------------------------------------------------------------------------- 1 | package tabify 2 | 3 | // nodeValue is a helper type to store a final value of json 4 | // defined by the keys[] - ie the json path to access to this value. 5 | type nodeValue struct { 6 | eventType nodeEventType 7 | key string 8 | value interface{} 9 | deep int 10 | } 11 | 12 | type nodeEventType uint8 13 | 14 | const ( 15 | readValue nodeEventType = iota 16 | startRow 17 | endRow 18 | ) 19 | -------------------------------------------------------------------------------- /tabify/options.go: -------------------------------------------------------------------------------- 1 | package tabify 2 | 3 | import "strings" 4 | 5 | // Options are our tabify options 6 | type Options struct { 7 | KeyFormatter KeyFormatterFunc 8 | KeyExcluder KeyExcluderFunc 9 | } 10 | 11 | // Option is an option setter 12 | type Option func(o *Options) 13 | 14 | // KeyFormatterFunc is a function to format a key from the input json 15 | type KeyFormatterFunc func([]string) string 16 | 17 | // KeyExcluderFunc is a function to exclude a key from the input json 18 | type KeyExcluderFunc func([]string) bool 19 | 20 | func newOptions(opt ...Option) Options { 21 | opts := Options{} 22 | 23 | for _, o := range opt { 24 | o(&opts) 25 | } 26 | 27 | // Need at least one formatter 28 | if opts.KeyFormatter == nil { 29 | KeyFormatter(defaultFormatter)(&opts) 30 | } 31 | 32 | return opts 33 | } 34 | 35 | func defaultFormatter(keys []string) string { 36 | return strings.Join(keys, "#") 37 | } 38 | 39 | // KeyFormatter sets the key formatter 40 | // default : func (keys []string) => strings.Join(keys, "#") 41 | func KeyFormatter(v KeyFormatterFunc) Option { 42 | return func(opts *Options) { 43 | if v != nil { 44 | opts.KeyFormatter = v 45 | } 46 | } 47 | } 48 | 49 | // KeyExcluder sets the key excluder 50 | // default : nil 51 | func KeyExcluder(v KeyExcluderFunc) Option { 52 | return func(opts *Options) { 53 | opts.KeyExcluder = v 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tabify/tabify.go: -------------------------------------------------------------------------------- 1 | package tabify 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/datasweet/jsonmap" 7 | ) 8 | 9 | // Tabify using a custom TableWriter 10 | func Tabify(j *jsonmap.Json, writer TableWriter, opt ...Option) error { 11 | if jsonmap.IsNil(j) { 12 | return errors.New("nil json") 13 | } 14 | if writer == nil { 15 | return errors.New("nil table writer") 16 | } 17 | t := newTabify(opt...) 18 | if err := t.Compute(j, writer); err != nil { 19 | return err 20 | } 21 | return nil 22 | } 23 | 24 | // JSON to flatten a json 25 | func JSON(j *jsonmap.Json, opt ...Option) ([]*jsonmap.Json, error) { 26 | writer := &jsonTableWriter{} 27 | if err := Tabify(j, writer, opt...); err != nil { 28 | return nil, err 29 | } 30 | return writer.JSON(), nil 31 | } 32 | 33 | // Slice to tabify into a slice array. 34 | // Note : first row contains headers 35 | func Slice(j *jsonmap.Json, opt ...Option) ([][]interface{}, error) { 36 | writer := &sliceTableWriter{} 37 | if err := Tabify(j, writer, opt...); err != nil { 38 | return nil, err 39 | } 40 | return writer.Table(), nil 41 | } 42 | 43 | // Map to tabify into a map array 44 | func Map(j *jsonmap.Json, opt ...Option) ([]map[string]interface{}, error) { 45 | writer := &mapTableWriter{} 46 | if err := Tabify(j, writer, opt...); err != nil { 47 | return nil, err 48 | } 49 | return writer.Table(), nil 50 | } 51 | 52 | // tabify is our main implementation 53 | type tabify struct { 54 | opts Options 55 | nodes chan *nodeValue 56 | } 57 | 58 | func newTabify(opt ...Option) *tabify { 59 | opts := newOptions(opt...) 60 | 61 | return &tabify{ 62 | opts: opts, 63 | } 64 | } 65 | 66 | func (t *tabify) Options() Options { 67 | return t.opts 68 | } 69 | 70 | func (t *tabify) Compute(json *jsonmap.Json, tw TableWriter) error { 71 | if jsonmap.IsNil(json) { 72 | return errors.New("no json provided") 73 | } 74 | 75 | t.nodes = make(chan *nodeValue) 76 | tb := newTableBuffer() 77 | 78 | go func() { 79 | defer close(t.nodes) 80 | t.collect(json) 81 | }() 82 | 83 | // Listen new node entry 84 | for node := range t.nodes { 85 | 86 | // We need a new row ! 87 | switch node.eventType { 88 | case startRow: 89 | //fmt.Println(strings.Repeat(" ", node.deep), "NEWR", node.key) 90 | tb.openRow() 91 | 92 | case endRow: 93 | //fmt.Println(strings.Repeat(" ", node.deep), "ENDR", node.key) 94 | tb.closeRow() 95 | default: 96 | //fmt.Println(strings.Repeat(" ", node.deep), "CELL ", node.key, " = ", node.value) 97 | tb.cell(node) 98 | } 99 | } 100 | 101 | // Write table 102 | tb.write(tw) 103 | 104 | return nil 105 | } 106 | 107 | // collects node in json 108 | func (t *tabify) collect(node *jsonmap.Json, keys ...string) { 109 | if jsonmap.IsNil(node) { 110 | return 111 | } 112 | if t.opts.KeyExcluder != nil && len(keys) > 0 && t.opts.KeyExcluder(keys) { 113 | return 114 | } 115 | if node.IsObject() { 116 | for _, key := range node.Keys() { 117 | t.collect(node.Get(key), append(keys, key)...) 118 | } 119 | } else if node.IsArray() { 120 | nvalues := node.Values() 121 | for _, item := range nvalues { 122 | t.nodes <- &nodeValue{ 123 | eventType: startRow, 124 | key: t.opts.KeyFormatter(keys), 125 | deep: len(keys), 126 | } 127 | 128 | t.collect(item, keys...) 129 | 130 | t.nodes <- &nodeValue{ 131 | eventType: endRow, 132 | key: t.opts.KeyFormatter(keys), 133 | deep: len(keys), 134 | } 135 | } 136 | } else { 137 | t.nodes <- &nodeValue{ 138 | eventType: readValue, 139 | key: t.opts.KeyFormatter(keys), 140 | deep: len(keys), 141 | value: node.Data(), 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tabify/tabify_test.go: -------------------------------------------------------------------------------- 1 | package tabify_test 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/datasweet/jsonmap" 10 | "github.com/datasweet/jsonmap/tabify" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestMetricAgg(t *testing.T) { 15 | testfile(t, "metric_agg") 16 | } 17 | 18 | func TestHistogramAgg(t *testing.T) { 19 | testfile(t, "histogram_terms_agg") 20 | } 21 | 22 | func TestNested(t *testing.T) { 23 | testfile(t, "nested_agg") 24 | } 25 | 26 | func TestDoubleTerms(t *testing.T) { 27 | testfile(t, "terms_terms_sum_agg") 28 | } 29 | 30 | func TestTripleAgg(t *testing.T) { 31 | testfile(t, "triple_agg") 32 | } 33 | 34 | func testfile(t *testing.T, filename string) { 35 | jsonf := readJSON(t, "./tests/"+filename+".json") 36 | src := jsonmap.FromString(jsonf).Get("aggregations") 37 | 38 | // Options excluder / formatter 39 | excluder := func(keys []string) bool { 40 | last := keys[len(keys)-1] 41 | return last == "doc_count_error_upper_bound" || last == "sum_other_doc_count" 42 | } 43 | 44 | formatter := func(keys []string) string { 45 | var nk []string 46 | for _, k := range keys { 47 | if k == "buckets" || k == "value" || k == "key" { 48 | continue 49 | } 50 | nk = append(nk, k) 51 | } 52 | 53 | return strings.Join(nk, "#") 54 | } 55 | 56 | // SliceTableWriter 57 | t.Run("can write to slice "+filename, func(t *testing.T) { 58 | st, err := tabify.Slice(src, tabify.KeyExcluder(excluder), tabify.KeyFormatter(formatter)) 59 | assert.NoError(t, err, "slice table writer") 60 | stw := readJSON(t, "./tests/"+filename+"_slice.json") 61 | var arr []interface{} 62 | assert.NoError(t, json.Unmarshal([]byte(stw), &arr)) 63 | assert.Equal(t, len(arr), len(st)) 64 | for i, row := range arr { 65 | assert.Equalf(t, row, st[i], "at index %d", i) 66 | } 67 | 68 | //assert.JSONEq(t, stw, mst.String(), "slice table writer") 69 | }) 70 | 71 | // MapTableWriter 72 | t.Run("can write to map "+filename, func(t *testing.T) { 73 | mt, err := tabify.Map(src, tabify.KeyExcluder(excluder), tabify.KeyFormatter(formatter)) 74 | assert.NoError(t, err, "map table writer") 75 | mmt, err := json.Marshal(mt) 76 | if err != nil { 77 | t.Fatal("slice json marshal") 78 | } 79 | mtw := readJSON(t, "./tests/"+filename+"_expected.json") 80 | assert.JSONEq(t, mtw, string(mmt[:]), "map table writer") 81 | }) 82 | 83 | // JSONTableWriter 84 | t.Run("can write to json "+filename, func(t *testing.T) { 85 | jt, err := tabify.JSON(src, tabify.KeyExcluder(excluder), tabify.KeyFormatter(formatter)) 86 | assert.NoError(t, err, "json table writer") 87 | jtw := readJSON(t, "./tests/"+filename+"_expected.json") 88 | jo := jsonmap.New() 89 | jo.Set("", jt) 90 | assert.JSONEq(t, jtw, jo.Stringify(), "json table writer") 91 | }) 92 | } 93 | 94 | // readJSON to read a json file and store to a jsonmap 95 | func readJSON(t *testing.T, filename string) string { 96 | data, err := os.ReadFile(filename) 97 | if err != nil { 98 | t.Fatalf("unable to read %s", filename) 99 | } 100 | return string(data[:]) 101 | } 102 | -------------------------------------------------------------------------------- /tabify/table_buffer.go: -------------------------------------------------------------------------------- 1 | package tabify 2 | 3 | type tableBuffer struct { 4 | deep int 5 | buffers map[int][]*rowBuffer 6 | } 7 | 8 | // newTableBuffer to create a new table in memory 9 | func newTableBuffer() *tableBuffer { 10 | buffers := make(map[int][]*rowBuffer) 11 | return &tableBuffer{ 12 | buffers: buffers, 13 | } 14 | } 15 | 16 | // openRow to create a new row 17 | func (tb *tableBuffer) openRow() { 18 | rb := &rowBuffer{ 19 | parent: len(tb.buffers[tb.deep]) - 1, 20 | } 21 | 22 | tb.deep++ 23 | tb.buffers[tb.deep] = append(tb.buffers[tb.deep], rb) 24 | } 25 | 26 | // closeRow to close the current row and write values 27 | func (tb *tableBuffer) closeRow() { 28 | if tb.deep > 0 { 29 | tb.deep-- 30 | } 31 | } 32 | 33 | // cell to create a new cell in row 34 | func (tb *tableBuffer) cell(val *nodeValue) { 35 | if len(tb.buffers) == 0 { 36 | tb.openRow() 37 | } 38 | buffers := tb.buffers[tb.deep] 39 | // Patch for nested, because the last node is an empty array for an unknown reason 40 | if len(buffers) > 0 { 41 | curr := buffers[len(buffers)-1] 42 | curr.values = append(curr.values, val) 43 | } 44 | } 45 | 46 | func (tb *tableBuffer) getMaxDeep() int { 47 | var max int 48 | for d := range tb.buffers { 49 | if d > max { 50 | max = d 51 | } 52 | } 53 | return max 54 | } 55 | 56 | // write our buffer into a tablewriter 57 | func (tb *tableBuffer) write(tw TableWriter) { 58 | if tb.deep > 0 { 59 | tb.closeRow() 60 | } 61 | 62 | max := tb.getMaxDeep() 63 | 64 | for _, row := range tb.buffers[max] { 65 | tw.OpenRow() 66 | 67 | for _, cell := range row.values { 68 | tw.Cell(cell.key, cell.value, cell.deep) 69 | } 70 | 71 | curr := row 72 | for deep := max - 1; deep > 0; deep-- { 73 | prow := tb.buffers[deep][curr.parent] 74 | for _, pcell := range prow.values { 75 | tw.Cell(pcell.key, pcell.value, pcell.deep) 76 | } 77 | curr = prow 78 | } 79 | tw.CloseRow() 80 | } 81 | } 82 | 83 | type rowBuffer struct { 84 | parent int 85 | values []*nodeValue 86 | } 87 | -------------------------------------------------------------------------------- /tabify/table_writer.go: -------------------------------------------------------------------------------- 1 | package tabify 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/datasweet/jsonmap" 7 | ) 8 | 9 | // TableWriter is an interface to define a table writer 10 | type TableWriter interface { 11 | OpenRow() 12 | Cell(k string, v interface{}, deep int) 13 | CloseRow() 14 | } 15 | 16 | // jsonTableWriter to write a json array 17 | type jsonTableWriter struct { 18 | row *jsonmap.Json 19 | table []*jsonmap.Json 20 | } 21 | 22 | func (w *jsonTableWriter) OpenRow() { 23 | w.row = jsonmap.New() 24 | } 25 | 26 | func (w *jsonTableWriter) Cell(k string, v interface{}, deep int) { 27 | if w.row == nil { 28 | w.OpenRow() 29 | } 30 | w.row.Set(k, v) 31 | } 32 | 33 | func (w *jsonTableWriter) CloseRow() { 34 | w.table = append(w.table, w.row) 35 | } 36 | 37 | func (w *jsonTableWriter) JSON() []*jsonmap.Json { 38 | return w.table 39 | } 40 | 41 | // mapTableWriter to write to a map 42 | type mapTableWriter struct { 43 | row map[string]interface{} 44 | table []map[string]interface{} 45 | } 46 | 47 | func (w *mapTableWriter) OpenRow() { 48 | w.row = make(map[string]interface{}) 49 | } 50 | 51 | func (w *mapTableWriter) Cell(k string, v interface{}, deep int) { 52 | w.row[k] = v 53 | } 54 | 55 | func (w *mapTableWriter) CloseRow() { 56 | w.table = append(w.table, w.row) 57 | } 58 | 59 | func (w *mapTableWriter) Table() []map[string]interface{} { 60 | return w.table 61 | } 62 | 63 | // sliceTableWriter to writes into a slice 64 | type sliceTableWriter struct { 65 | cols []*sliceCol 66 | row map[string]interface{} 67 | table [][]interface{} 68 | len int 69 | } 70 | 71 | type sliceCol struct { 72 | name string 73 | deep int 74 | } 75 | 76 | func (w *sliceTableWriter) OpenRow() { 77 | w.row = make(map[string]interface{}) 78 | } 79 | 80 | func (w *sliceTableWriter) Cell(k string, v interface{}, deep int) { 81 | if w.len == 0 { 82 | w.cols = append(w.cols, &sliceCol{k, deep}) 83 | } 84 | w.row[k] = v 85 | } 86 | 87 | func (w *sliceTableWriter) CloseRow() { 88 | if w.len == 0 { 89 | // compute cols 90 | sort.Slice(w.cols, func(i, j int) bool { 91 | if w.cols[i].deep == w.cols[j].deep { 92 | return w.cols[i].name < w.cols[j].name 93 | } 94 | return w.cols[i].deep < w.cols[j].deep 95 | }) 96 | 97 | c := make([]interface{}, len(w.cols)) 98 | for i, col := range w.cols { 99 | c[i] = col.name 100 | } 101 | w.table = append(w.table, c) 102 | } 103 | 104 | r := make([]interface{}, len(w.cols)) 105 | for i, col := range w.cols { 106 | if v, ok := w.row[col.name]; ok { 107 | r[i] = v 108 | } 109 | } 110 | 111 | w.table = append(w.table, r) 112 | w.len++ 113 | } 114 | 115 | func (w *sliceTableWriter) Table() [][]interface{} { 116 | return w.table 117 | } 118 | -------------------------------------------------------------------------------- /tabify/tests/histogram_terms_agg.json: -------------------------------------------------------------------------------- 1 | { 2 | "took": 98, 3 | "timed_out": false, 4 | "_shards": { 5 | "total": 5, 6 | "successful": 5, 7 | "failed": 0 8 | }, 9 | "hits": { 10 | "total": 768068, 11 | "max_score": 0, 12 | "hits": [] 13 | }, 14 | "aggregations": { 15 | "2": { 16 | "buckets": [ 17 | { 18 | "4": { 19 | "doc_count_error_upper_bound": -1, 20 | "sum_other_doc_count": 186054, 21 | "buckets": [ 22 | { 23 | "3": { 24 | "value": 129913 25 | }, 26 | "key": "_PRENOMS_RARES", 27 | "doc_count": 767 28 | }, 29 | { 30 | "3": { 31 | "value": 36853 32 | }, 33 | "key": "LÉA", 34 | "doc_count": 383 35 | }, 36 | { 37 | "3": { 38 | "value": 33469 39 | }, 40 | "key": "LUCAS", 41 | "doc_count": 385 42 | }, 43 | { 44 | "3": { 45 | "value": 31912 46 | }, 47 | "key": "THÉO", 48 | "doc_count": 384 49 | }, 50 | { 51 | "3": { 52 | "value": 31572 53 | }, 54 | "key": "THOMAS", 55 | "doc_count": 383 56 | } 57 | ] 58 | }, 59 | "key": 2000, 60 | "doc_count": 188356 61 | }, 62 | { 63 | "4": { 64 | "doc_count_error_upper_bound": -1, 65 | "sum_other_doc_count": 254761, 66 | "buckets": [ 67 | { 68 | "3": { 69 | "value": 205893 70 | }, 71 | "key": "_PRENOMS_RARES", 72 | "doc_count": 959 73 | }, 74 | { 75 | "3": { 76 | "value": 38138 77 | }, 78 | "key": "ENZO", 79 | "doc_count": 480 80 | }, 81 | { 82 | "3": { 83 | "value": 35994 84 | }, 85 | "key": "LUCAS", 86 | "doc_count": 480 87 | }, 88 | { 89 | "3": { 90 | "value": 34296 91 | }, 92 | "key": "EMMA", 93 | "doc_count": 480 94 | }, 95 | { 96 | "3": { 97 | "value": 31166 98 | }, 99 | "key": "NATHAN", 100 | "doc_count": 478 101 | } 102 | ] 103 | }, 104 | "key": 2005, 105 | "doc_count": 257638 106 | }, 107 | { 108 | "4": { 109 | "doc_count_error_upper_bound": -1, 110 | "sum_other_doc_count": 266780, 111 | "buckets": [ 112 | { 113 | "3": { 114 | "value": 264390 115 | }, 116 | "key": "_PRENOMS_RARES", 117 | "doc_count": 960 118 | }, 119 | { 120 | "3": { 121 | "value": 29881 122 | }, 123 | "key": "LUCAS", 124 | "doc_count": 479 125 | }, 126 | { 127 | "3": { 128 | "value": 27309 129 | }, 130 | "key": "NATHAN", 131 | "doc_count": 480 132 | }, 133 | { 134 | "3": { 135 | "value": 26791 136 | }, 137 | "key": "EMMA", 138 | "doc_count": 476 139 | }, 140 | { 141 | "3": { 142 | "value": 24937 143 | }, 144 | "key": "GABRIEL", 145 | "doc_count": 477 146 | } 147 | ] 148 | }, 149 | "key": 2010, 150 | "doc_count": 269652 151 | }, 152 | { 153 | "4": { 154 | "doc_count_error_upper_bound": -1, 155 | "sum_other_doc_count": 51877, 156 | "buckets": [ 157 | { 158 | "3": { 159 | "value": 56107 160 | }, 161 | "key": "_PRENOMS_RARES", 162 | "doc_count": 192 163 | }, 164 | { 165 | "3": { 166 | "value": 5147 167 | }, 168 | "key": "JULES", 169 | "doc_count": 96 170 | }, 171 | { 172 | "3": { 173 | "value": 5061 174 | }, 175 | "key": "GABRIEL", 176 | "doc_count": 77 177 | }, 178 | { 179 | "3": { 180 | "value": 4540 181 | }, 182 | "key": "LOUISE", 183 | "doc_count": 96 184 | }, 185 | { 186 | "3": { 187 | "value": 4377 188 | }, 189 | "key": "ADAM", 190 | "doc_count": 84 191 | } 192 | ] 193 | }, 194 | "key": 2015, 195 | "doc_count": 52422 196 | } 197 | ] 198 | } 199 | }, 200 | "status": 200 201 | } -------------------------------------------------------------------------------- /tabify/tests/histogram_terms_agg_expected.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "2": 2000, "2#doc_count": 188356, "2#4": "_PRENOMS_RARES", "2#4#doc_count": 767, "2#4#3": 129913 }, 3 | { "2": 2000, "2#doc_count": 188356, "2#4": "LÉA", "2#4#doc_count": 383, "2#4#3": 36853 }, 4 | { "2": 2000, "2#doc_count": 188356, "2#4": "LUCAS", "2#4#doc_count": 385, "2#4#3": 33469 }, 5 | { "2": 2000, "2#doc_count": 188356, "2#4": "THÉO", "2#4#doc_count": 384, "2#4#3": 31912 }, 6 | { "2": 2000, "2#doc_count": 188356, "2#4": "THOMAS", "2#4#doc_count": 383, "2#4#3": 31572 }, 7 | { "2": 2005, "2#doc_count": 257638, "2#4": "_PRENOMS_RARES", "2#4#doc_count": 959, "2#4#3": 205893 }, 8 | { "2": 2005, "2#doc_count": 257638, "2#4": "ENZO", "2#4#doc_count": 480, "2#4#3": 38138 }, 9 | { "2": 2005, "2#doc_count": 257638, "2#4": "LUCAS", "2#4#doc_count": 480, "2#4#3": 35994 }, 10 | { "2": 2005, "2#doc_count": 257638, "2#4": "EMMA", "2#4#doc_count": 480, "2#4#3": 34296 }, 11 | { "2": 2005, "2#doc_count": 257638, "2#4": "NATHAN", "2#4#doc_count": 478, "2#4#3": 31166 }, 12 | { "2": 2010, "2#doc_count": 269652, "2#4": "_PRENOMS_RARES", "2#4#doc_count": 960, "2#4#3": 264390 }, 13 | { "2": 2010, "2#doc_count": 269652, "2#4": "LUCAS", "2#4#doc_count": 479, "2#4#3": 29881 }, 14 | { "2": 2010, "2#doc_count": 269652, "2#4": "NATHAN", "2#4#doc_count": 480, "2#4#3": 27309 }, 15 | { "2": 2010, "2#doc_count": 269652, "2#4": "EMMA", "2#4#doc_count": 476, "2#4#3": 26791 }, 16 | { "2": 2010, "2#doc_count": 269652, "2#4": "GABRIEL", "2#4#doc_count": 477, "2#4#3": 24937 }, 17 | { "2": 2015, "2#doc_count": 52422, "2#4": "_PRENOMS_RARES", "2#4#doc_count": 192, "2#4#3": 56107 }, 18 | { "2": 2015, "2#doc_count": 52422, "2#4": "JULES", "2#4#doc_count": 96, "2#4#3": 5147 }, 19 | { "2": 2015, "2#doc_count": 52422, "2#4": "GABRIEL", "2#4#doc_count": 77, "2#4#3": 5061 }, 20 | { "2": 2015, "2#doc_count": 52422, "2#4": "LOUISE", "2#4#doc_count": 96, "2#4#3": 4540 }, 21 | { "2": 2015, "2#doc_count": 52422, "2#4": "ADAM", "2#4#doc_count": 84, "2#4#3": 4377 } 22 | ] 23 | -------------------------------------------------------------------------------- /tabify/tests/histogram_terms_agg_slice.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ "2", "2#doc_count", "2#4", "2#4#doc_count", "2#4#3" ], 3 | [ 2000, 188356, "_PRENOMS_RARES", 767, 129913 ], 4 | [ 2000, 188356, "LÉA", 383, 36853 ], 5 | [ 2000, 188356, "LUCAS", 385, 33469 ], 6 | [ 2000, 188356, "THÉO", 384, 31912 ], 7 | [ 2000, 188356, "THOMAS", 383, 31572 ], 8 | [ 2005, 257638, "_PRENOMS_RARES", 959, 205893 ], 9 | [ 2005, 257638, "ENZO", 480, 38138 ], 10 | [ 2005, 257638, "LUCAS", 480, 35994 ], 11 | [ 2005, 257638, "EMMA", 480, 34296 ], 12 | [ 2005, 257638, "NATHAN", 478, 31166 ], 13 | [ 2010, 269652, "_PRENOMS_RARES", 960, 264390 ], 14 | [ 2010, 269652, "LUCAS", 479, 29881 ], 15 | [ 2010, 269652, "NATHAN", 480, 27309 ], 16 | [ 2010, 269652, "EMMA", 476, 26791 ], 17 | [ 2010, 269652, "GABRIEL", 477, 24937 ], 18 | [ 2015, 52422, "_PRENOMS_RARES", 192, 56107 ], 19 | [ 2015, 52422, "JULES", 96, 5147 ], 20 | [ 2015, 52422, "GABRIEL", 77, 5061 ], 21 | [ 2015, 52422, "LOUISE", 96, 4540 ], 22 | [ 2015, 52422, "ADAM", 84, 4377 ] 23 | ] 24 | -------------------------------------------------------------------------------- /tabify/tests/metric_agg.json: -------------------------------------------------------------------------------- 1 | { 2 | "took": 82, 3 | "timed_out": false, 4 | "_shards": { 5 | "total": 5, 6 | "successful": 5, 7 | "failed": 0 8 | }, 9 | "hits": { 10 | "total": 3372275, 11 | "max_score": 0, 12 | "hits": [] 13 | }, 14 | "aggregations": { 15 | "sum_prenoms": { 16 | "value": 75426044 17 | } 18 | }, 19 | "status": 200 20 | } -------------------------------------------------------------------------------- /tabify/tests/metric_agg_expected.json: -------------------------------------------------------------------------------- 1 | [ { "sum_prenoms": 75426044 } ] -------------------------------------------------------------------------------- /tabify/tests/metric_agg_slice.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ "sum_prenoms" ], 3 | [ 75426044 ] 4 | ] 5 | -------------------------------------------------------------------------------- /tabify/tests/nested_agg.json: -------------------------------------------------------------------------------- 1 | { 2 | "took": 0, 3 | "timed_out": false, 4 | "_shards": { 5 | "total": 5, 6 | "successful": 5, 7 | "skipped": 0, 8 | "failed": 0 9 | }, 10 | "hits": { 11 | "total": 2, 12 | "max_score": 0, 13 | "hits": [] 14 | }, 15 | "aggregations": { 16 | "byType": { 17 | "doc_count_error_upper_bound": 0, 18 | "sum_other_doc_count": 0, 19 | "buckets": [ 20 | { 21 | "key": "Final", 22 | "doc_count": 1, 23 | "affirmations": { 24 | "doc_count": 160, 25 | "byPivot": { 26 | "doc_count_error_upper_bound": 0, 27 | "sum_other_doc_count": 0, 28 | "buckets": [ 29 | { 30 | "key": "1", 31 | "doc_count": 32, 32 | "sumWeight": { 33 | "value": 0 34 | } 35 | }, 36 | { 37 | "key": "2", 38 | "doc_count": 16, 39 | "sumWeight": { 40 | "value": 12.900000154972076 41 | } 42 | }, 43 | { 44 | "key": "3", 45 | "doc_count": 48, 46 | "sumWeight": { 47 | "value": 42.4000004529953 48 | } 49 | }, 50 | { 51 | "key": "4", 52 | "doc_count": 40, 53 | "sumWeight": { 54 | "value": 32.400000393390656 55 | } 56 | }, 57 | { 58 | "key": "5", 59 | "doc_count": 20, 60 | "sumWeight": { 61 | "value": 18.000000178813934 62 | } 63 | }, 64 | { 65 | "key": "6", 66 | "doc_count": 4, 67 | "sumWeight": { 68 | "value": 3.900000035762787 69 | } 70 | } 71 | ] 72 | } 73 | } 74 | }, 75 | { 76 | "key": "Initial", 77 | "doc_count": 1, 78 | "affirmations": { 79 | "doc_count": 160, 80 | "byPivot": { 81 | "doc_count_error_upper_bound": 0, 82 | "sum_other_doc_count": 0, 83 | "buckets": [ 84 | { 85 | "key": "1", 86 | "doc_count": 32, 87 | "sumWeight": { 88 | "value": 0 89 | } 90 | }, 91 | { 92 | "key": "2", 93 | "doc_count": 16, 94 | "sumWeight": { 95 | "value": 12.20000010728836 96 | } 97 | }, 98 | { 99 | "key": "3", 100 | "doc_count": 48, 101 | "sumWeight": { 102 | "value": 39.10000038146973 103 | } 104 | }, 105 | { 106 | "key": "4", 107 | "doc_count": 40, 108 | "sumWeight": { 109 | "value": 36.500000298023224 110 | } 111 | }, 112 | { 113 | "key": "5", 114 | "doc_count": 20, 115 | "sumWeight": { 116 | "value": 17.200000166893005 117 | } 118 | }, 119 | { 120 | "key": "6", 121 | "doc_count": 4, 122 | "sumWeight": { 123 | "value": 2.600000023841858 124 | } 125 | } 126 | ] 127 | } 128 | } 129 | } 130 | ] 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tabify/tests/nested_agg_expected.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "byType": "Final", 4 | "byType#doc_count": 1, 5 | "byType#affirmations#doc_count": 160, 6 | "byType#affirmations#byPivot": "1", 7 | "byType#affirmations#byPivot#doc_count": 32, 8 | "byType#affirmations#byPivot#sumWeight": 0 9 | }, 10 | { 11 | "byType": "Final", 12 | "byType#doc_count": 1, 13 | "byType#affirmations#doc_count": 160, 14 | "byType#affirmations#byPivot": "2", 15 | "byType#affirmations#byPivot#doc_count": 16, 16 | "byType#affirmations#byPivot#sumWeight": 12.900000154972076 17 | }, 18 | { 19 | "byType": "Final", 20 | "byType#doc_count": 1, 21 | "byType#affirmations#doc_count": 160, 22 | "byType#affirmations#byPivot": "3", 23 | "byType#affirmations#byPivot#doc_count": 48, 24 | "byType#affirmations#byPivot#sumWeight": 42.4000004529953 25 | }, 26 | { 27 | "byType": "Final", 28 | "byType#doc_count": 1, 29 | "byType#affirmations#doc_count": 160, 30 | "byType#affirmations#byPivot": "4", 31 | "byType#affirmations#byPivot#doc_count": 40, 32 | "byType#affirmations#byPivot#sumWeight": 32.400000393390656 33 | }, 34 | { 35 | "byType": "Final", 36 | "byType#doc_count": 1, 37 | "byType#affirmations#doc_count": 160, 38 | "byType#affirmations#byPivot": "5", 39 | "byType#affirmations#byPivot#doc_count": 20, 40 | "byType#affirmations#byPivot#sumWeight": 18.000000178813934 41 | }, 42 | { 43 | "byType": "Final", 44 | "byType#doc_count": 1, 45 | "byType#affirmations#doc_count": 160, 46 | "byType#affirmations#byPivot": "6", 47 | "byType#affirmations#byPivot#doc_count": 4, 48 | "byType#affirmations#byPivot#sumWeight": 3.900000035762787 49 | }, 50 | { 51 | "byType": "Initial", 52 | "byType#doc_count": 1, 53 | "byType#affirmations#doc_count": 160, 54 | "byType#affirmations#byPivot": "1", 55 | "byType#affirmations#byPivot#doc_count": 32, 56 | "byType#affirmations#byPivot#sumWeight": 0 57 | }, 58 | { 59 | "byType": "Initial", 60 | "byType#doc_count": 1, 61 | "byType#affirmations#doc_count": 160, 62 | "byType#affirmations#byPivot": "2", 63 | "byType#affirmations#byPivot#doc_count": 16, 64 | "byType#affirmations#byPivot#sumWeight": 12.20000010728836 65 | }, 66 | { 67 | "byType": "Initial", 68 | "byType#doc_count": 1, 69 | "byType#affirmations#doc_count": 160, 70 | "byType#affirmations#byPivot": "3", 71 | "byType#affirmations#byPivot#doc_count": 48, 72 | "byType#affirmations#byPivot#sumWeight": 39.10000038146973 73 | }, 74 | { 75 | "byType": "Initial", 76 | "byType#doc_count": 1, 77 | "byType#affirmations#doc_count": 160, 78 | "byType#affirmations#byPivot": "4", 79 | "byType#affirmations#byPivot#doc_count": 40, 80 | "byType#affirmations#byPivot#sumWeight": 36.500000298023224 81 | }, 82 | { 83 | "byType": "Initial", 84 | "byType#doc_count": 1, 85 | "byType#affirmations#doc_count": 160, 86 | "byType#affirmations#byPivot": "5", 87 | "byType#affirmations#byPivot#doc_count": 20, 88 | "byType#affirmations#byPivot#sumWeight": 17.200000166893005 89 | }, 90 | { 91 | "byType": "Initial", 92 | "byType#doc_count": 1, 93 | "byType#affirmations#doc_count": 160, 94 | "byType#affirmations#byPivot": "6", 95 | "byType#affirmations#byPivot#doc_count": 4, 96 | "byType#affirmations#byPivot#sumWeight": 2.600000023841858 97 | } 98 | ] 99 | -------------------------------------------------------------------------------- /tabify/tests/nested_agg_slice.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ "byType", "byType#doc_count", "byType#affirmations#doc_count", "byType#affirmations#byPivot", "byType#affirmations#byPivot#doc_count", "byType#affirmations#byPivot#sumWeight" ], 3 | [ "Final", 1, 160, "1", 32, 0 ], 4 | [ "Final", 1, 160, "2", 16, 12.900000154972076 ], 5 | [ "Final", 1, 160, "3", 48, 42.4000004529953 ], 6 | [ "Final", 1, 160, "4", 40, 32.400000393390656 ], 7 | [ "Final", 1, 160, "5", 20, 18.000000178813934 ], 8 | [ "Final", 1, 160, "6", 4, 3.900000035762787 ], 9 | [ "Initial", 1, 160, "1", 32, 0 ], 10 | [ "Initial", 1, 160, "2", 16, 12.20000010728836 ], 11 | [ "Initial", 1, 160, "3", 48, 39.10000038146973 ], 12 | [ "Initial", 1, 160, "4", 40, 36.500000298023224 ], 13 | [ "Initial", 1, 160, "5", 20, 17.200000166893005 ], 14 | [ "Initial", 1, 160, "6", 4, 2.600000023841858 ] 15 | ] 16 | -------------------------------------------------------------------------------- /tabify/tests/terms_terms_sum_agg.json: -------------------------------------------------------------------------------- 1 | { 2 | "took": 4, 3 | "timed_out": false, 4 | "_shards": { 5 | "total": 5, 6 | "successful": 5, 7 | "skipped": 0, 8 | "failed": 0 9 | }, 10 | "hits": { 11 | "total": 111396, 12 | "max_score": 0, 13 | "hits": [] 14 | }, 15 | "aggregations": { 16 | "2": { 17 | "doc_count_error_upper_bound": -1, 18 | "sum_other_doc_count": 105158, 19 | "buckets": [ 20 | { 21 | "1": { 22 | "value": 94547 23 | }, 24 | "3": { 25 | "doc_count_error_upper_bound": 0, 26 | "sum_other_doc_count": 0, 27 | "buckets": [ 28 | { 29 | "1": { 30 | "value": 94547 31 | }, 32 | "key": "Hamlet", 33 | "doc_count": 1582 34 | } 35 | ] 36 | }, 37 | "key": "HAMLET", 38 | "doc_count": 1582 39 | }, 40 | { 41 | "1": { 42 | "value": 76899 43 | }, 44 | "3": { 45 | "doc_count_error_upper_bound": 0, 46 | "sum_other_doc_count": 0, 47 | "buckets": [ 48 | { 49 | "1": { 50 | "value": 76899 51 | }, 52 | "key": "Othello", 53 | "doc_count": 1161 54 | } 55 | ] 56 | }, 57 | "key": "IAGO", 58 | "doc_count": 1161 59 | }, 60 | { 61 | "1": { 62 | "value": 71866 63 | }, 64 | "3": { 65 | "doc_count_error_upper_bound": 0, 66 | "sum_other_doc_count": 0, 67 | "buckets": [ 68 | { 69 | "1": { 70 | "value": 71866 71 | }, 72 | "key": "Loves Labours Lost", 73 | "doc_count": 647 74 | } 75 | ] 76 | }, 77 | "key": "BIRON", 78 | "doc_count": 647 79 | }, 80 | { 81 | "1": { 82 | "value": 62945 83 | }, 84 | "3": { 85 | "doc_count_error_upper_bound": 0, 86 | "sum_other_doc_count": 0, 87 | "buckets": [ 88 | { 89 | "1": { 90 | "value": 62945 91 | }, 92 | "key": "Othello", 93 | "doc_count": 928 94 | } 95 | ] 96 | }, 97 | "key": "OTHELLO", 98 | "doc_count": 928 99 | }, 100 | { 101 | "1": { 102 | "value": 60243 103 | }, 104 | "3": { 105 | "doc_count_error_upper_bound": 0, 106 | "sum_other_doc_count": 5, 107 | "buckets": [ 108 | { 109 | "1": { 110 | "value": 28740 111 | }, 112 | "key": "Richard III", 113 | "doc_count": 743 114 | }, 115 | { 116 | "1": { 117 | "value": 9809 118 | }, 119 | "key": "King Lear", 120 | "doc_count": 364 121 | }, 122 | { 123 | "1": { 124 | "value": 9238 125 | }, 126 | "key": "Henry VI Part 3", 127 | "doc_count": 266 128 | }, 129 | { 130 | "1": { 131 | "value": 9156 132 | }, 133 | "key": "Henry VI Part 2", 134 | "doc_count": 320 135 | }, 136 | { 137 | "1": { 138 | "value": 3161 139 | }, 140 | "key": "Henry VI Part 1", 141 | "doc_count": 222 142 | } 143 | ] 144 | }, 145 | "key": "GLOUCESTER", 146 | "doc_count": 1920 147 | } 148 | ] 149 | } 150 | }, 151 | "status": 200 152 | } 153 | -------------------------------------------------------------------------------- /tabify/tests/terms_terms_sum_agg_expected.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "2": "HAMLET", "2#doc_count": 1582, "2#1": 94547, "2#3": "Hamlet", "2#3#doc_count": 1582, "2#3#1": 94547 }, 3 | { "2": "IAGO", "2#doc_count": 1161, "2#1": 76899, "2#3": "Othello", "2#3#doc_count": 1161, "2#3#1": 76899 }, 4 | { "2": "BIRON", "2#doc_count": 647, "2#1": 71866, "2#3": "Loves Labours Lost", "2#3#doc_count": 647, "2#3#1": 71866 }, 5 | { "2": "OTHELLO", "2#doc_count": 928, "2#1": 62945, "2#3": "Othello", "2#3#doc_count": 928, "2#3#1": 62945 }, 6 | { "2": "GLOUCESTER", "2#doc_count": 1920, "2#1": 60243, "2#3": "Richard III", "2#3#doc_count": 743, "2#3#1": 28740 }, 7 | { "2": "GLOUCESTER", "2#doc_count": 1920, "2#1": 60243, "2#3": "King Lear", "2#3#doc_count": 364, "2#3#1": 9809 }, 8 | { "2": "GLOUCESTER", "2#doc_count": 1920, "2#1": 60243, "2#3": "Henry VI Part 3", "2#3#doc_count": 266, "2#3#1": 9238 }, 9 | { "2": "GLOUCESTER", "2#doc_count": 1920, "2#1": 60243, "2#3": "Henry VI Part 2", "2#3#doc_count": 320, "2#3#1": 9156 }, 10 | { "2": "GLOUCESTER", "2#doc_count": 1920, "2#1": 60243, "2#3": "Henry VI Part 1", "2#3#doc_count": 222, "2#3#1": 3161 } 11 | ] 12 | -------------------------------------------------------------------------------- /tabify/tests/terms_terms_sum_agg_slice.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["2", "2#doc_count", "2#1", "2#3", "2#3#doc_count", "2#3#1"], 3 | ["HAMLET",1582,94547,"Hamlet",1582,94547 ], 4 | ["IAGO",1161,76899,"Othello",1161,76899 ], 5 | ["BIRON",647,71866,"Loves Labours Lost",647,71866 ], 6 | ["OTHELLO",928,62945,"Othello",928,62945 ], 7 | ["GLOUCESTER",1920,60243,"Richard III",743,28740 ], 8 | ["GLOUCESTER",1920,60243,"King Lear",364,9809 ], 9 | ["GLOUCESTER",1920,60243,"Henry VI Part 3",266,9238 ], 10 | ["GLOUCESTER",1920,60243,"Henry VI Part 2",320,9156 ], 11 | ["GLOUCESTER",1920,60243,"Henry VI Part 1",222,3161] 12 | ] 13 | -------------------------------------------------------------------------------- /tabify/tests/triple_agg.json: -------------------------------------------------------------------------------- 1 | { 2 | "took": 1, 3 | "timed_out": false, 4 | "_shards": { 5 | "total": 5, 6 | "successful": 5, 7 | "skipped": 0, 8 | "failed": 0 9 | }, 10 | "hits": { 11 | "total": 15810, 12 | "max_score": 0, 13 | "hits": [] 14 | }, 15 | "aggregations": { 16 | "2": { 17 | "buckets": [ 18 | { 19 | "3": { 20 | "buckets": [ 21 | { 22 | "4": { 23 | "doc_count_error_upper_bound": -1, 24 | "sum_other_doc_count": 2385, 25 | "buckets": [ 26 | { 27 | "1": { 28 | "value": 255187660 29 | }, 30 | "key": "53", 31 | "doc_count": 156 32 | }, 33 | { 34 | "1": { 35 | "value": 242023980 36 | }, 37 | "key": "38", 38 | "doc_count": 181 39 | }, 40 | { 41 | "1": { 42 | "value": 177748981 43 | }, 44 | "key": "13", 45 | "doc_count": 235 46 | }, 47 | { 48 | "1": { 49 | "value": 160380530 50 | }, 51 | "key": "57", 52 | "doc_count": 179 53 | }, 54 | { 55 | "1": { 56 | "value": 149837920 57 | }, 58 | "key": "62", 59 | "doc_count": 110 60 | } 61 | ] 62 | }, 63 | "key": 7, 64 | "doc_count": 3246 65 | }, 66 | { 67 | "4": { 68 | "doc_count_error_upper_bound": -1, 69 | "sum_other_doc_count": 2194, 70 | "buckets": [ 71 | { 72 | "1": { 73 | "value": 156764260 74 | }, 75 | "key": "38", 76 | "doc_count": 188 77 | }, 78 | { 79 | "1": { 80 | "value": 133461606 81 | }, 82 | "key": "57", 83 | "doc_count": 181 84 | }, 85 | { 86 | "1": { 87 | "value": 125295235 88 | }, 89 | "key": "13", 90 | "doc_count": 198 91 | }, 92 | { 93 | "1": { 94 | "value": 122926445 95 | }, 96 | "key": "53", 97 | "doc_count": 140 98 | }, 99 | { 100 | "1": { 101 | "value": 110164820 102 | }, 103 | "key": "62", 104 | "doc_count": 121 105 | } 106 | ] 107 | }, 108 | "key": 8, 109 | "doc_count": 3022 110 | }, 111 | { 112 | "4": { 113 | "doc_count_error_upper_bound": -1, 114 | "sum_other_doc_count": 2465, 115 | "buckets": [ 116 | { 117 | "1": { 118 | "value": 254565135 119 | }, 120 | "key": "38", 121 | "doc_count": 195 122 | }, 123 | { 124 | "1": { 125 | "value": 215214320 126 | }, 127 | "key": "53", 128 | "doc_count": 116 129 | }, 130 | { 131 | "1": { 132 | "value": 153625560 133 | }, 134 | "key": "57", 135 | "doc_count": 149 136 | }, 137 | { 138 | "1": { 139 | "value": 148241771 140 | }, 141 | "key": "13", 142 | "doc_count": 207 143 | }, 144 | { 145 | "1": { 146 | "value": 124179771 147 | }, 148 | "key": "62", 149 | "doc_count": 98 150 | } 151 | ] 152 | }, 153 | "key": 9, 154 | "doc_count": 3230 155 | } 156 | ] 157 | }, 158 | "key": 2017, 159 | "doc_count": 9498 160 | }, 161 | { 162 | "3": { 163 | "buckets": [ 164 | { 165 | "4": { 166 | "doc_count_error_upper_bound": -1, 167 | "sum_other_doc_count": 2521, 168 | "buckets": [ 169 | { 170 | "1": { 171 | "value": 273097085 172 | }, 173 | "key": "38", 174 | "doc_count": 172 175 | }, 176 | { 177 | "1": { 178 | "value": 254692570 179 | }, 180 | "key": "53", 181 | "doc_count": 140 182 | }, 183 | { 184 | "1": { 185 | "value": 191291965 186 | }, 187 | "key": "13", 188 | "doc_count": 204 189 | }, 190 | { 191 | "1": { 192 | "value": 176124380 193 | }, 194 | "key": "57", 195 | "doc_count": 154 196 | }, 197 | { 198 | "1": { 199 | "value": 168446946 200 | }, 201 | "key": "62", 202 | "doc_count": 130 203 | } 204 | ] 205 | }, 206 | "key": 7, 207 | "doc_count": 3321 208 | }, 209 | { 210 | "4": { 211 | "doc_count_error_upper_bound": -1, 212 | "sum_other_doc_count": 2167, 213 | "buckets": [ 214 | { 215 | "1": { 216 | "value": 158230375 217 | }, 218 | "key": "38", 219 | "doc_count": 173 220 | }, 221 | { 222 | "1": { 223 | "value": 141642890 224 | }, 225 | "key": "13", 226 | "doc_count": 211 227 | }, 228 | { 229 | "1": { 230 | "value": 137446790 231 | }, 232 | "key": "57", 233 | "doc_count": 195 234 | }, 235 | { 236 | "1": { 237 | "value": 128803970 238 | }, 239 | "key": "62", 240 | "doc_count": 121 241 | }, 242 | { 243 | "1": { 244 | "value": 110624770 245 | }, 246 | "key": "53", 247 | "doc_count": 124 248 | } 249 | ] 250 | }, 251 | "key": 8, 252 | "doc_count": 2991 253 | } 254 | ] 255 | }, 256 | "key": 2018, 257 | "doc_count": 6312 258 | } 259 | ] 260 | } 261 | }, 262 | "status": 200 263 | } 264 | -------------------------------------------------------------------------------- /tabify/tests/triple_agg_expected.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "2#3#4#1":255187660, "2#3#4":"53", "2#3#4#doc_count":156, "2#3":7, "2#3#doc_count":3246, "2":2017, "2#doc_count":9498 }, 3 | { "2#3#4#1":242023980, "2#3#4":"38", "2#3#4#doc_count":181, "2#3":7, "2#3#doc_count":3246, "2":2017, "2#doc_count":9498 }, 4 | { "2#3#4#1":177748981, "2#3#4":"13", "2#3#4#doc_count":235, "2#3":7, "2#3#doc_count":3246, "2":2017, "2#doc_count":9498 }, 5 | { "2#3#4#1":160380530, "2#3#4":"57", "2#3#4#doc_count":179, "2#3":7, "2#3#doc_count":3246, "2":2017, "2#doc_count":9498 }, 6 | { "2#3#4#1":149837920, "2#3#4":"62", "2#3#4#doc_count":110, "2#3":7, "2#3#doc_count":3246, "2":2017, "2#doc_count":9498 }, 7 | { "2#3#4#1":156764260, "2#3#4":"38", "2#3#4#doc_count":188, "2#3":8, "2#3#doc_count":3022, "2":2017, "2#doc_count":9498 }, 8 | { "2#3#4#1":133461606, "2#3#4":"57", "2#3#4#doc_count":181, "2#3":8, "2#3#doc_count":3022, "2":2017, "2#doc_count":9498 }, 9 | { "2#3#4#1":125295235, "2#3#4":"13", "2#3#4#doc_count":198, "2#3":8, "2#3#doc_count":3022, "2":2017, "2#doc_count":9498 }, 10 | { "2#3#4#1":122926445, "2#3#4":"53", "2#3#4#doc_count":140, "2#3":8, "2#3#doc_count":3022, "2":2017, "2#doc_count":9498 }, 11 | { "2#3#4#1":110164820, "2#3#4":"62", "2#3#4#doc_count":121, "2#3":8, "2#3#doc_count":3022, "2":2017, "2#doc_count":9498 }, 12 | { "2#3#4#1":254565135, "2#3#4":"38", "2#3#4#doc_count":195, "2#3":9, "2#3#doc_count":3230, "2":2017, "2#doc_count":9498 }, 13 | { "2#3#4#1":215214320, "2#3#4":"53", "2#3#4#doc_count":116, "2#3":9, "2#3#doc_count":3230, "2":2017, "2#doc_count":9498 }, 14 | { "2#3#4#1":153625560, "2#3#4":"57", "2#3#4#doc_count":149, "2#3":9, "2#3#doc_count":3230, "2":2017, "2#doc_count":9498 }, 15 | { "2#3#4#1":148241771, "2#3#4":"13", "2#3#4#doc_count":207, "2#3":9, "2#3#doc_count":3230, "2":2017, "2#doc_count":9498 }, 16 | { "2#3#4#1":124179771, "2#3#4":"62", "2#3#4#doc_count":98, "2#3":9, "2#3#doc_count":3230, "2":2017, "2#doc_count":9498 }, 17 | { "2#3#4#1":273097085, "2#3#4":"38", "2#3#4#doc_count":172, "2#3":7, "2#3#doc_count":3321, "2":2018, "2#doc_count":6312 }, 18 | { "2#3#4#1":254692570, "2#3#4":"53", "2#3#4#doc_count":140, "2#3":7, "2#3#doc_count":3321, "2":2018, "2#doc_count":6312 }, 19 | { "2#3#4#1":191291965, "2#3#4":"13", "2#3#4#doc_count":204, "2#3":7, "2#3#doc_count":3321, "2":2018, "2#doc_count":6312 }, 20 | { "2#3#4#1":176124380, "2#3#4":"57", "2#3#4#doc_count":154, "2#3":7, "2#3#doc_count":3321, "2":2018, "2#doc_count":6312 }, 21 | { "2#3#4#1":168446946, "2#3#4":"62", "2#3#4#doc_count":130, "2#3":7, "2#3#doc_count":3321, "2":2018, "2#doc_count":6312 }, 22 | { "2#3#4#1":158230375, "2#3#4":"38", "2#3#4#doc_count":173, "2#3":8, "2#3#doc_count":2991, "2":2018, "2#doc_count":6312 }, 23 | { "2#3#4#1":141642890, "2#3#4":"13", "2#3#4#doc_count":211, "2#3":8, "2#3#doc_count":2991, "2":2018, "2#doc_count":6312 }, 24 | { "2#3#4#1":137446790, "2#3#4":"57", "2#3#4#doc_count":195, "2#3":8, "2#3#doc_count":2991, "2":2018, "2#doc_count":6312 }, 25 | { "2#3#4#1":128803970, "2#3#4":"62", "2#3#4#doc_count":121, "2#3":8, "2#3#doc_count":2991, "2":2018, "2#doc_count":6312 }, 26 | { "2#3#4#1":110624770, "2#3#4":"53", "2#3#4#doc_count":124, "2#3":8, "2#3#doc_count":2991, "2":2018, "2#doc_count":6312 } 27 | ] 28 | -------------------------------------------------------------------------------- /tabify/tests/triple_agg_slice.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ "2", "2#doc_count", "2#3", "2#3#doc_count", "2#3#4", "2#3#4#doc_count", "2#3#4#1" ], 3 | [ 2017, 9498, 7, 3246, "53", 156, 255187660 ], 4 | [ 2017, 9498, 7, 3246, "38", 181, 242023980 ], 5 | [ 2017, 9498, 7, 3246, "13", 235, 177748981 ], 6 | [ 2017, 9498, 7, 3246, "57", 179, 160380530 ], 7 | [ 2017, 9498, 7, 3246, "62", 110, 149837920 ], 8 | [ 2017, 9498, 8, 3022, "38", 188, 156764260 ], 9 | [ 2017, 9498, 8, 3022, "57", 181, 133461606 ], 10 | [ 2017, 9498, 8, 3022, "13", 198, 125295235 ], 11 | [ 2017, 9498, 8, 3022, "53", 140, 122926445 ], 12 | [ 2017, 9498, 8, 3022, "62", 121, 110164820 ], 13 | [ 2017, 9498, 9, 3230, "38", 195, 254565135 ], 14 | [ 2017, 9498, 9, 3230, "53", 116, 215214320 ], 15 | [ 2017, 9498, 9, 3230, "57", 149, 153625560 ], 16 | [ 2017, 9498, 9, 3230, "13", 207, 148241771 ], 17 | [ 2017, 9498, 9, 3230, "62", 98 , 124179771 ], 18 | [ 2018, 6312, 7, 3321, "38", 172, 273097085 ], 19 | [ 2018, 6312, 7, 3321, "53", 140, 254692570 ], 20 | [ 2018, 6312, 7, 3321, "13", 204, 191291965 ], 21 | [ 2018, 6312, 7, 3321, "57", 154, 176124380 ], 22 | [ 2018, 6312, 7, 3321, "62", 130, 168446946 ], 23 | [ 2018, 6312, 8, 2991, "38", 173, 158230375 ], 24 | [ 2018, 6312, 8, 2991, "13", 211, 141642890 ], 25 | [ 2018, 6312, 8, 2991, "57", 195, 137446790 ], 26 | [ 2018, 6312, 8, 2991, "62", 121, 128803970 ], 27 | [ 2018, 6312, 8, 2991, "53", 124, 110624770 ] 28 | ] 29 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package jsonmap 2 | 3 | import "strings" 4 | 5 | func createPath(path string) []string { 6 | var keys []string 7 | infos := strings.Split(path, ".") 8 | var tmp strings.Builder 9 | for _, k := range infos { 10 | if len(k) > 0 && k[len(k)-1] == '\\' { 11 | tmp.WriteString(k[:len(k)-1]) 12 | tmp.WriteString(".") 13 | continue 14 | } else { 15 | tmp.WriteString(k) 16 | } 17 | parts := strings.Split(tmp.String(), "[") 18 | for _, p := range parts { 19 | t := strings.TrimSpace(strings.TrimSuffix(p, "]")) 20 | if len(t) > 0 { 21 | keys = append(keys, t) 22 | } 23 | } 24 | tmp.Reset() 25 | } 26 | return keys 27 | } 28 | 29 | // EscapePath to escape a path 30 | // Example 31 | // - By default jsonmap.Set("message.raw", "hello world !") 32 | // => { "message": { "raw": "hello world !" }} 33 | // - With escape jsonmap.Set(jsonmap.EscapePath("message.raw"), "hello world !") 34 | // => { "message.raw": "hello world !" }} 35 | func EscapePath(path string) string { 36 | return strings.Replace(path, ".", "\\.", -1) 37 | } 38 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package jsonmap_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/datasweet/jsonmap" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEscapePath(t *testing.T) { 11 | json := jsonmap.New() 12 | json.Set("message.raw", "hello world !") 13 | assert.JSONEq(t, `{ "message": { "raw": "hello world !" }}`, json.Stringify()) 14 | 15 | json = jsonmap.New() 16 | json.Set(jsonmap.EscapePath("message.raw"), "hello world !") 17 | assert.JSONEq(t, `{ "message.raw": "hello world !" }`, json.Stringify()) 18 | } 19 | --------------------------------------------------------------------------------