├── UNLICENSE ├── jpipe.go └── README.md /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /jpipe.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "gopkg.in/alecthomas/kingpin.v2" 8 | "io" 9 | "log" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "unicode/utf8" 14 | ) 15 | 16 | var ( 17 | keySep = kingpin.Flag("key", "Key separator character").Short('k').Default("/").String() 18 | columnSep = kingpin.Flag("column", "Column separator character").Short('c').Default("\t").String() 19 | nullDelimited = kingpin.Flag("null", "Separate key-value pairs with \\0 instead of newlines").Short('z').Bool() 20 | ascii = kingpin.Flag("ascii", "Ensure JSON strings are output only in ASCII format").Short('a').Bool() 21 | ) 22 | 23 | type jsonNode struct { 24 | keyPath []string 25 | encodedValue string 26 | } 27 | 28 | func extendKeyPath(keyPath []string, key string) []string { 29 | newKeyPath := make([]string, len(keyPath), len(keyPath)+1) 30 | copy(newKeyPath, keyPath) 31 | return append(newKeyPath, key) 32 | } 33 | 34 | func unwrap(keyPath []string, obj interface{}, output chan jsonNode) { 35 | switch obj := obj.(type) { 36 | default: 37 | // This should never happen 38 | log.Println("Can't decode object %s", obj) 39 | case string: 40 | if *ascii { 41 | output <- jsonNode{keyPath, encodeStringAsASCII(obj)} 42 | } else { 43 | marshalledBytes, _ := json.Marshal(obj) 44 | output <- jsonNode{keyPath, string(marshalledBytes)} 45 | } 46 | case bool, nil: 47 | marshalledBytes, _ := json.Marshal(obj) 48 | output <- jsonNode{keyPath, string(marshalledBytes)} 49 | case json.Number: 50 | output <- jsonNode{keyPath, obj.String()} 51 | case map[string]interface{}: 52 | output <- jsonNode{keyPath, "{}"} 53 | for k, v := range obj { 54 | if strings.Contains(k, *keySep) { 55 | log.Println(fmt.Sprintf( 56 | "key \"%s\" contains key separator \"%s\"", k, *keySep)) 57 | os.Exit(1) 58 | } 59 | unwrap(extendKeyPath(keyPath, k), v, output) 60 | } 61 | case []interface{}: 62 | output <- jsonNode{keyPath, "[]"} 63 | for i, v := range obj { 64 | unwrap(extendKeyPath(keyPath, strconv.Itoa(i)), v, output) 65 | } 66 | } 67 | } 68 | 69 | var hex = "0123456789abcdef" 70 | 71 | func encodeStringAsASCII(str string) string { 72 | var output bytes.Buffer 73 | output.WriteByte('"') 74 | for _, b := range bytes.Runes([]byte(str)) { 75 | if b < utf8.RuneSelf { 76 | switch b { 77 | case '\\', '"': 78 | output.WriteByte('\\') 79 | output.WriteByte(byte(b)) 80 | case '\n': 81 | output.WriteByte('\\') 82 | output.WriteByte('n') 83 | case '\r': 84 | output.WriteByte('\\') 85 | output.WriteByte('r') 86 | case '\t': 87 | output.WriteByte('\\') 88 | output.WriteByte('t') 89 | default: 90 | if b < 0x20 || b == '<' || b == '>' || b == '&' { 91 | output.WriteString(`\u00`) 92 | output.WriteByte(hex[b>>4]) 93 | output.WriteByte(hex[b&0xF]) 94 | } else { 95 | output.WriteByte(byte(b)) 96 | } 97 | } 98 | } else { 99 | output.WriteString(fmt.Sprintf("\\u%04x", b)) 100 | } 101 | } 102 | output.WriteByte('"') 103 | return output.String() 104 | } 105 | 106 | func main() { 107 | kingpin.Parse() 108 | 109 | dec := json.NewDecoder(os.Stdin) 110 | dec.UseNumber() 111 | output := make(chan jsonNode) 112 | done := make(chan int32) 113 | go func() { 114 | for node := range output { 115 | os.Stdout.WriteString(*keySep) 116 | os.Stdout.WriteString(strings.Join(node.keyPath, *keySep)) 117 | os.Stdout.WriteString(*columnSep) 118 | os.Stdout.WriteString(node.encodedValue) 119 | if *nullDelimited { 120 | os.Stdout.WriteString("\x00") 121 | } else { 122 | os.Stdout.WriteString("\n") 123 | } 124 | os.Stdout.Sync() 125 | } 126 | done <- 1 127 | }() 128 | for { 129 | var v interface{} 130 | if err := dec.Decode(&v); err != nil { 131 | if err != io.EOF { 132 | log.Println("ERROR: %s", err) 133 | } 134 | close(output) 135 | break 136 | } 137 | unwrap(make([]string, 0), v, output) 138 | } 139 | <-done 140 | } 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jpipe 2 | 3 | jpipe is a Go rewrite of my Python [jsonpipe][] tool. It's written to be easier 4 | to distribute (thanks to Go's static linking). It should be faster, too—but 5 | don't rely on this tool for performance! 6 | 7 | [jsonpipe]: https://github.com/zacharyvoase/jsonpipe 8 | 9 | 10 | ## Installation 11 | 12 | If you have a `GOPATH` you can just: 13 | 14 | go install github.com/zacharyvoase/jpipe 15 | 16 | Pre-built binaries are also available from the [releases page][]. 17 | 18 | [releases page]: https://github.com/zacharyvoase/jpipe/releases 19 | 20 | ## Example 21 | 22 | A `
` is worth a thousand words. For simple JSON values:
 23 | 
 24 |     $ echo '"Hello, World!"' | jpipe
 25 |     /   "Hello, World!"
 26 |     $ echo 123 | jpipe
 27 |     /   123
 28 |     $ echo 0.25 | jpipe
 29 |     /   0.25
 30 |     $ echo null | jpipe
 31 |     /   null
 32 |     $ echo true | jpipe
 33 |     /   true
 34 |     $ echo false | jpipe
 35 |     /   false
 36 | 
 37 | The 'root' of the object tree is represented by a single `/` character,
 38 | and for simple values it doesn't get any more complex than the above.
 39 | Note that a single tab character separates the path on the left from the
 40 | literal value on the right.
 41 | 
 42 | Composite data structures use a hierarchical syntax, where individual
 43 | keys/indices are children of the path to the containing object:
 44 | 
 45 |     $ echo '{"a": 1, "b": 2}' | jpipe
 46 |     /   {}
 47 |     /a  1
 48 |     /b  2
 49 |     $ echo '["foo", "bar", "baz"]' | jpipe
 50 |     /   []
 51 |     /0  "foo"
 52 |     /1  "bar"
 53 |     /2  "baz"
 54 | 
 55 | For an object or array, the right-hand column indicates the datatype,
 56 | and will be either `{}` (object) or `[]` (array). For objects, the order
 57 | of the keys is preserved in the output.
 58 | 
 59 | The path syntax allows arbitrarily complex data structures:
 60 | 
 61 |     $ echo '[{"a": [{"b": {"c": ["foo"]}}]}]' | jpipe
 62 |     /   []
 63 |     /0  {}
 64 |     /0/a    []
 65 |     /0/a/0  {}
 66 |     /0/a/0/b    {}
 67 |     /0/a/0/b/c  []
 68 |     /0/a/0/b/c/0    "foo"
 69 | 
 70 | 
 71 | ## Caveat: Path Separators
 72 | 
 73 | Because the path components are separated by `/` characters, an object
 74 | key like `"abc/def"` would result in ambiguous output. jpipe will
 75 | throw an error if this occurs in your input, so that you can recognize
 76 | and handle the issue. To mitigate the problem, you can choose a
 77 | different path separator:
 78 | 
 79 |     $ echo '{"abc/def": 123}' | jpipe -k '☃'
 80 |     ☃   {}
 81 |     ☃abc/def    123
 82 | 
 83 | The Unicode snowman is chosen here because it's unlikely to occur as
 84 | part of the key in most JSON objects, but any character or string (e.g.
 85 | `:`, `::`, `~`) will do.
 86 | 
 87 | 
 88 | ## Unlicense
 89 | 
 90 | This is free and unencumbered software released into the public domain.
 91 | 
 92 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
 93 | software, either in source code form or as a compiled binary, for any purpose,
 94 | commercial or non-commercial, and by any means.
 95 | 
 96 | In jurisdictions that recognize copyright laws, the author or authors of this
 97 | software dedicate any and all copyright interest in the software to the public
 98 | domain. We make this dedication for the benefit of the public at large and to
 99 | the detriment of our heirs and successors. We intend this dedication to be an
100 | overt act of relinquishment in perpetuity of all present and future rights to
101 | this software under copyright law.
102 | 
103 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
104 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
105 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
106 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
107 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
108 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
109 | 
110 | For more information, please refer to 
111 | 


--------------------------------------------------------------------------------