-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 35cf945
Showing
13 changed files
with
1,891 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2019 Volumental AB | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
# JSONTemplate | ||
|
||
`jsontemplate` is a JSON transformation and templating language and library implemented in Go. | ||
|
||
In simple terms, it renders arbitrary JSON structures based on a JSON-like template definition, populating it with data from an some other JSON structure. | ||
|
||
## Feature overview | ||
|
||
- Low-clutter template syntax, aimed to look similar to the final output. | ||
- [JSONPath](https://goessner.net/articles/JsonPath/) expressions to fetch values from the input. | ||
- Array generator expressions with subtemplates, allowing mapping of arrays of objects. | ||
- Ability to call Go functions from within a template. | ||
|
||
## Getting started | ||
|
||
The following is a complete but minimal program that loads a template and transforms an input JSON object using it. | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
|
||
"github.com/Volumental/jsontemplate" | ||
) | ||
|
||
const input = `{ "snakeCase": 123 }` | ||
|
||
func main() { | ||
template, _ := jsontemplate.ParseString(`{ "CamelCase": $.snakeCase }`, nil) | ||
template.RenderJSON(os.Stdout, strings.NewReader(input)) | ||
os.Stdout.Sync() | ||
} | ||
``` | ||
|
||
Running the above program will output: | ||
``` | ||
{"CamelCase":123} | ||
``` | ||
|
||
## Features by example | ||
|
||
This example illustrates some of the features in `jsontemplate`. For further details, please see the library documentation. | ||
|
||
Consider the following input JSON structure: | ||
|
||
```json | ||
{ | ||
"store": { | ||
"book": [ | ||
{ | ||
"category": "reference", | ||
"author": "Nigel Rees", | ||
"title": "Sayings of the Century", | ||
"price": 8.95 | ||
}, | ||
{ | ||
"category": "fiction", | ||
"author": "Evelyn Waugh", | ||
"title": "Sword of Honour", | ||
"price": 12.99 | ||
}, | ||
{ | ||
"category": "fiction", | ||
"author": "Herman Melville", | ||
"title": "Moby Dick", | ||
"isbn": "0-553-21311-3", | ||
"price": 8.99 | ||
}, | ||
{ | ||
"category": "fiction", | ||
"author": "J. R. R. Tolkien", | ||
"title": "The Lord of the Rings", | ||
"isbn": "0-395-19395-8", | ||
"price": 22.99 | ||
} | ||
], | ||
"bicycle": { | ||
"color": "red", | ||
"price": 19.95 | ||
} | ||
} | ||
} | ||
``` | ||
|
||
When fed through the following template: | ||
|
||
``` | ||
{ | ||
# Pick an invidual field. | ||
"bicycle_color": $.store.bicycle.color, | ||
"book_info": { | ||
# Slice an array, taking the first three elements. | ||
"top_three": $.store.book[:3], | ||
# Map a list of objects. | ||
"price_list": range $.store.book[*] [ | ||
{ | ||
"title": $.title, | ||
"price": $.price, | ||
} | ||
], | ||
}, | ||
# Calculate the average of all price fields by calling a Go function. | ||
"avg_price": Avg($..price), | ||
} | ||
``` | ||
|
||
...the following output is yielded: | ||
|
||
```json | ||
{ | ||
"avg_price": 14.774000000000001, | ||
"bicycle_color": "red", | ||
"book_info": { | ||
"price_list": [ | ||
{ | ||
"price": 8.95, | ||
"title": "Sayings of the Century" | ||
}, | ||
{ | ||
"price": 12.99, | ||
"title": "Sword of Honour" | ||
}, | ||
{ | ||
"price": 8.99, | ||
"title": "Moby Dick" | ||
}, | ||
{ | ||
"price": 22.99, | ||
"title": "The Lord of the Rings" | ||
} | ||
], | ||
"top_three": [ | ||
{ | ||
"author": "Nigel Rees", | ||
"category": "reference", | ||
"price": 8.95, | ||
"title": "Sayings of the Century" | ||
}, | ||
{ | ||
"author": "Evelyn Waugh", | ||
"category": "fiction", | ||
"price": 12.99, | ||
"title": "Sword of Honour" | ||
}, | ||
{ | ||
"author": "Herman Melville", | ||
"category": "fiction", | ||
"isbn": "0-553-21311-3", | ||
"price": 8.99, | ||
"title": "Moby Dick" | ||
} | ||
] | ||
} | ||
} | ||
``` | ||
|
||
## Performance | ||
|
||
`jsontemplate` has first and foremost been designed with correctness and ease of use in mind. As such, optimum performance has not been the primary objective. Nevertheless, you can expect to see in the order of 10 MB/s on a single CPU core, around half of which is JSON parsing/encoding. We expect this to be more than adequate for most production use-cases. | ||
|
||
## Maturity | ||
|
||
`jsontemplate` is provided as-is, and you should assume it has bugs. That said, at the time of writing, the library is being used for production workloads at [Volumental](https://www.volumental.com). | ||
|
||
Until a 1.0 release is made, incompatible changes may occur, though we will generally strive to maintain full backwards compatibility. Incompatible changes to the template definition format are unlikely to be introduced at this point. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package jsontemplate_test | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/Volumental/jsontemplate" | ||
) | ||
|
||
// Store example from JSONPath. | ||
const benchmarkInput = ` | ||
{ | ||
"foo": { | ||
"bar": [ | ||
{ | ||
"text": "this is a benchmark test", | ||
"number": 12345.6789, | ||
"bool": true, | ||
"array": [1, 2, 3, "hello", true] | ||
}, | ||
{ | ||
"text": "short" | ||
}, | ||
{ | ||
"text": "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooonger", | ||
"number": 0, | ||
"bool": false, | ||
"array": [1, 2, 3, "hello", true] | ||
}, | ||
{ | ||
"text": "this is a second benchmark test", | ||
"number": 4711, | ||
"bool": null | ||
}, | ||
{ | ||
"text": "this is the final benchmark test", | ||
"array": ["nice", 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] | ||
} | ||
] | ||
} | ||
} | ||
` | ||
|
||
const benchmarkTemplate = ` | ||
{ | ||
"arrays": range $..bar [$.array[0]], | ||
"mapping": range $.foo.bar[:4] [ | ||
{ | ||
"string": ToUpper($.text), | ||
"num": $.number, | ||
} | ||
], | ||
"a_text": $.foo.bar[3].text, | ||
} | ||
` | ||
|
||
var result interface{} | ||
|
||
func Benchmark_core(b *testing.B) { | ||
var funcs = jsontemplate.FunctionMap{"ToUpper": strings.ToUpper} | ||
var template, err = jsontemplate.ParseString(benchmarkTemplate, funcs) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
b.SetBytes(int64(len(benchmarkInput))) | ||
|
||
var input interface{} | ||
if err := json.Unmarshal([]byte(benchmarkInput), &input); err != nil { | ||
panic(err) | ||
} | ||
|
||
var output interface{} | ||
for n := 0; n < b.N; n++ { | ||
var err error | ||
output, err = template.Render(input) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
result = output | ||
} | ||
|
||
func Benchmark_full(b *testing.B) { | ||
var funcs = jsontemplate.FunctionMap{"ToUpper": strings.ToUpper} | ||
var template, err = jsontemplate.ParseString(benchmarkTemplate, funcs) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
b.SetBytes(int64(len(benchmarkInput))) | ||
|
||
var input interface{} | ||
if err := json.Unmarshal([]byte(benchmarkInput), &input); err != nil { | ||
panic(err) | ||
} | ||
|
||
var output interface{} | ||
for n := 0; n < b.N; n++ { | ||
if err = template.RenderJSON(ioutil.Discard, strings.NewReader(benchmarkInput)); err != nil { | ||
panic(err) | ||
} | ||
} | ||
result = output | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package jsontemplate_test | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
|
||
"github.com/Volumental/jsontemplate" | ||
) | ||
|
||
// Store example from JSONPath. | ||
const Input = ` | ||
{ | ||
"store": { | ||
"book": [ | ||
{ | ||
"category": "reference", | ||
"author": "Nigel Rees", | ||
"title": "Sayings of the Century", | ||
"price": 8.95 | ||
}, | ||
{ | ||
"category": "fiction", | ||
"author": "Evelyn Waugh", | ||
"title": "Sword of Honour", | ||
"price": 12.99 | ||
}, | ||
{ | ||
"category": "fiction", | ||
"author": "Herman Melville", | ||
"title": "Moby Dick", | ||
"isbn": "0-553-21311-3", | ||
"price": 8.99 | ||
}, | ||
{ | ||
"category": "fiction", | ||
"author": "J. R. R. Tolkien", | ||
"title": "The Lord of the Rings", | ||
"isbn": "0-395-19395-8", | ||
"price": 22.99 | ||
} | ||
], | ||
"bicycle": { | ||
"color": "red", | ||
"price": 19.95 | ||
} | ||
} | ||
} | ||
` | ||
|
||
const Template = ` | ||
{ | ||
# Pick an invidual field. | ||
"bicycle_color": $.store.bicycle.color, | ||
"book_info": { | ||
# Slice an array, taking the first three elements. | ||
"top_three": $.store.book[:3], | ||
# Map a list of objects. | ||
"price_list": range $.store.book[*] [ | ||
{ | ||
"title": $.title, | ||
"price": $.price, | ||
} | ||
], | ||
}, | ||
# Calculate the average of all price fields. | ||
"avg_price": Avg($..price), | ||
} | ||
` | ||
|
||
// Helper function we'll use in the template. | ||
func Avg(values []interface{}) float64 { | ||
var sum = 0.0 | ||
var cnt = 0 | ||
for _, val := range values { | ||
if num, ok := val.(float64); ok { | ||
sum += num | ||
cnt += 1 | ||
} | ||
} | ||
return sum / float64(cnt) | ||
} | ||
|
||
func Example() { | ||
var funcs = jsontemplate.FunctionMap{"Avg": Avg} | ||
var template, _ = jsontemplate.ParseString(Template, funcs) | ||
|
||
template.RenderJSON(os.Stdout, strings.NewReader(Input)) | ||
os.Stdout.Sync() | ||
// Output: {"avg_price":14.774000000000001,"bicycle_color":"red","book_info":{"price_list":[{"price":8.95,"title":"Sayings of the Century"},{"price":12.99,"title":"Sword of Honour"},{"price":8.99,"title":"Moby Dick"},{"price":22.99,"title":"The Lord of the Rings"}],"top_three":[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Evelyn Waugh","category":"fiction","price":12.99,"title":"Sword of Honour"},{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"}]}} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package jsontemplate |
Oops, something went wrong.