diff --git a/README.md b/README.md index e838e86..48846f2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,53 @@ # JSON typedef infer -This is a port of [`json-typedef-infer`][jtd-infer] from Rust to Go. The reason -for porting this is that I was in need of JTD inference from code and not as a -CLI tool so I ported this straight from Rust. +This is a port of [`json-typedef-infer`][jtd-infer] Go. The reason for porting +this is that I was in need of JTD inference from code and not as a CLI tool. + +For more information about JSON Typedef and its RFC and how to use different +kind of hints see [`json-typedef-infer`][jtd-infer] + +## Usage + +See [examples] directory for runnable examples and how to infer JTD. + +```go +schema := NewInferrer(WithoutHints()). + Infer("my-string"). + IntoSchema(WithoutHints()) +// { +// "type": "string" +// } +``` + +If you have multiple rows of objects or lists as strings you can pass them to +the shorthand function `InferStrings`. + +```go +rows := []string{ + `{"name":"Joe", "age": 52, "something_optional": true, "something_nullable": 1.1}`, + `{"name":"Jane", "age": 48, "something_nullable": null}`, +} +schema := InferStrings(rows, WithoutHints()).IntoSchema(WithoutHints()) +// { +// "properties": { +// "age": { +// "type": "uint8" +// }, +// "name": { +// "type": "string" +// }, +// "something_nullable": { +// "nullable": true, +// "type": "float64" +// } +// }, +// "optionalProperties": { +// "something_optional": { +// "type": "boolean" +// } +// } +// } +``` [jtd-infer]: https://github.com/jsontypedef/json-typedef-infer/ +[examples]: examples diff --git a/examples/infer_multiple_string_rows.go b/examples/infer_multiple_string_rows.go new file mode 100644 index 0000000..aace2d3 --- /dev/null +++ b/examples/infer_multiple_string_rows.go @@ -0,0 +1,20 @@ +package main + +import ( + "encoding/json" + + "github.com/bombsimon/jtdinfer" +) + +func main() { + rows := []string{ + `{"name":"Joe", "age": 52, "something_optional": true, "something_nullable": 1.1}`, + `{"name":"Jane", "age": 48, "something_nullable": null}`, + } + schema := jtdinfer. + InferStrings(rows, jtdinfer.WithoutHints()). + IntoSchema(jtdinfer.WithoutHints()) + + j, _ := json.MarshalIndent(schema, "", " ") + print(string(j)) +} diff --git a/examples/infer_simple_value.go b/examples/infer_simple_value.go new file mode 100644 index 0000000..127cd70 --- /dev/null +++ b/examples/infer_simple_value.go @@ -0,0 +1,17 @@ +package main + +import ( + "encoding/json" + + "github.com/bombsimon/jtdinfer" +) + +func main() { + schema := jtdinfer. + NewInferrer(jtdinfer.WithoutHints()). + Infer("my-string"). + IntoSchema(jtdinfer.WithoutHints()) + + j, _ := json.MarshalIndent(schema, "", " ") + print(string(j)) +} diff --git a/examples/infer_with_hints.go b/examples/infer_with_hints.go new file mode 100644 index 0000000..d5c0b0a --- /dev/null +++ b/examples/infer_with_hints.go @@ -0,0 +1,36 @@ +package main + +import ( + "encoding/json" + + "github.com/bombsimon/jtdinfer" +) + +func main() { + rows := []string{ + `{ + "name":"Joe", + "age":52, + "work":{"department": "sales"}, + "values":{"x": [1, 2, 3], "y": [4, 5, 6], "z": [7, 8, 9]}, + "discriminator":[{"type":"s", "value":"foo"},{"type":"n", "value":3.14}] + }`, + `{ + "name":"Jane", + "age":48, + "work":{"department": "engineering"}, + "values":{"x": [1, 2, 3], "y": [4, 5, 6], "z": [7, 8, -2000]}, + "discriminator":[{"type":"s", "value":"foo"},{"type":"n", "value":3.14}] + }`, + } + hints := jtdinfer.Hints{ + DefaultNumType: jtdinfer.NumTypeUint32, + Enums: jtdinfer.NewHintSet().Add([]string{"work", "department"}), + Values: jtdinfer.NewHintSet().Add([]string{"values"}), + Discriminator: jtdinfer.NewHintSet().Add([]string{"discriminator", "-", "type"}), + } + + schema := jtdinfer.InferStrings(rows, hints).IntoSchema(hints) + j, _ := json.MarshalIndent(schema, "", " ") + print(string(j)) +} diff --git a/hints.go b/hints.go index 2a6b276..40f98b4 100644 --- a/hints.go +++ b/hints.go @@ -1,7 +1,10 @@ package jtdinfer +// Wildcard represents the character that matches any value for hints. const Wildcard = "-" +// Hints contains the default number type to use and all the hints for enums, +// values and discriminators. type Hints struct { DefaultNumType NumType Enums HintSet @@ -9,6 +12,11 @@ type Hints struct { Discriminator HintSet } +func WithoutHints() Hints { + return Hints{} +} + +// SubHint will return the sub hints for all hint sets for the passed key. func (h Hints) SubHints(key string) Hints { return Hints{ DefaultNumType: h.DefaultNumType, @@ -18,33 +26,42 @@ func (h Hints) SubHints(key string) Hints { } } +// IsEnumActive checks if the enum hint set is active. func (h Hints) IsEnumActive() bool { return h.Enums.IsActive() } +// IsValuesActive checks if the values hint set is active. func (h Hints) IsValuesActive() bool { return h.Values.IsActive() } +// PeekActiveDiscriminator will peek the currently active discriminator, if any. +// The returned boolean tells if there is an active discriminator. func (h Hints) PeekActiveDiscriminator() (string, bool) { return h.Discriminator.PeekActive() } +// HintSet represents a list of paths (lists) to match for hints. type HintSet struct { Values [][]string } +// NewHintSet creates a new empty `HintSet`. func NewHintSet() HintSet { return HintSet{ Values: [][]string{}, } } +// Add will add a path (slice) to the `HintSet`. func (h HintSet) Add(v []string) HintSet { h.Values = append(h.Values, v) return h } +// SubHint will filter all the current sets and keep those who's first element +// matches the passed key or wildcard. func (h HintSet) SubHints(key string) HintSet { filteredValues := [][]string{} @@ -64,6 +81,7 @@ func (h HintSet) SubHints(key string) HintSet { } } +// IsActive returns true if any set in the hint set his active. func (h HintSet) IsActive() bool { for _, valueList := range h.Values { if len(valueList) == 0 { @@ -74,6 +92,8 @@ func (h HintSet) IsActive() bool { return false } +// PeekActive returns the currently active value if any. The returned boolean +// tells if a value was found. func (h HintSet) PeekActive() (string, bool) { for _, values := range h.Values { if len(values) != 1 {