jsonstruct

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2022 License: BSD-2-Clause Imports: 13 Imported by: 0

README

go-jsonstruct

GoDoc Report Card Coverage Status

Generate Go structs from multiple JSON objects.

What does go-jsonstruct do and why should I use it?

go-jsonstruct generates Go structs from multiple JSON objects. Existing Go struct generators such as json-to-go and json2struct take only a single JSON object as input. go-jsonstruct takes multiple JSON objects as input and generates the most specific Go struct possible into which all the input objects can be unmarshalled.

This is useful if you have a collection of JSON objects, where no single object has all properties present, and you want to unmarshal those JSON objects into a Go program. Example collections include:

  • JSON responses received from a REST API with no defined schema.
  • Multiple values from a JSON column in an SQL database.
  • All the JSON documents in a document database.

How do I use go-jsonstruct?

Install go-jsonstruct:

go install github.com/twpayne/go-jsonstruct/cmd/gojsonstruct@latest

Feed it some JSON objects. For example you can feed it with

{
  "age": 37,
  "user_height_m": 2
}

{
  "age": 38,
  "user_height_m": 1.7,
  "favoriteFoods": [
    "cake"
  ]
}

by running

echo '{"age":37,"user_height_m":2}' \
    '{"age":38,"user_height_m":1.7,"favoriteFoods":["cake"]}' \
    | gojsonstruct

This will output:

package main

type T struct {
    Age           int      `json:"age"`
    FavoriteFoods []string `json:"favoriteFoods,omitempty"`
    UserHeightM   float64  `json:"user_height_m"`
}

This example demonstrates:

  • age is always observed as an integer, and so is given type int. The lower-case JSON property age is converted into an exported Go field name Age for compatibility with encoding/json.
  • favoriteFoods is observed as a JSON array, but is not always present, but when it is present it only contains JSON strings, and so is given type []string. The camelCase name favoriteFoods is converted into the exported Go field name FavoriteFoods and is tagged with omitempty.
  • user_height_m is observed as JSON numbers 2 and 1.7, for which the most specific Go type is float64. The snake_case name user_height_m is converted to the exported Go field name UserHeightM.
  • Properties are sorted alphabetically.

go-jsonstruct recursively handles nested array elements and objects. For example, given the following three JSON objects input:

{
  "nested": {
    "bar": true,
    "foo": "baz"
  }
}

{
  "nested": {
    "bar": false,
    "foo": null
  }
}

{
  "nested": {
    "bar": true,
    "foo": ""
  }
}

which you can try by running

echo '{"nested":{"bar":true,"foo":"baz"}}' \
    '{"nested":{"bar":false,"foo":null}}' \
    '{"nested":{"bar":true,"foo":""}}' \
    | gojsonstruct -packagename mypackage -typename MyType

generates the output

package mypackage

type MyType struct {
    Nested struct {
        Bar bool    `json:"bar"`
        Foo *string `json:"foo"`
    } `json:"nested"`
}

This demonstrates:

  • The package name and type name can be set on the command line.
  • The arbitrarily-complex property nested is recursively converted to a nested struct that all values can be unmarshalled to. go-jsonstruct handles array elements in an identical fashion, resolving array elements to the most-specific type.
  • nested.bar is always observed as a JSON bool, and is converted to a field of type bool.
  • nested.foo is observed as a JSON string, a JSON null, and an empty JSON string and is converted to a field of type *string without omitempty. With omitempty, Go's encoding/json omits the field in the two cases of nil and a pointer to an empty string, so there is no way to distinguish between the observed values of null and "". go-jsonstruct falls back to the option of *string without omitempty which means that a value is always present, even if empty.

You can feed it your own data via the standard input, for example if you have a file with one JSON object per line in objects.json you can run:

gojsonstruct < objects.json

To learn about more about the available options, run:

gojsonstruct -help

What are go-jsonstruct's key features?

  • Finds the most specific Go type that can represent all input values.
  • Generates Go struct field names from camelCase, kebab-case, and snake_case JSON object property names.
  • Capitalizes common abbreviations (e.g. HTTP, ID, and URL) when generating Go struct field names to follow Go conventions, with the option to add your own abbreviations.
  • Gives you control over the output, including the generated package name, type name, and godoc-compatible comments.
  • Generates deterministic output based only on the determined structure of the input, making it suitable for incorporation into build pipelines or detecting schema changes.
  • Generates omitempty when possible.
  • Uses the standard library's time.Time when possible.
  • Gracefully handles properties with spaces that cannot be unmarshalled by encoding/json.

How does go-jsonstruct work?

go-jsonstruct consists of two phases: observation and code generation.

Firstly, in the observation phase, go-jsonstruct explores all the input objects and records statistics on what types are observed in each part. It recurses into objects and iterates over arrays.

Secondly, in the code generation phase, go-jsonstruct inspects the gathered statistics and determines the strictest possible Go type that can represent all the observed values. For example, the values 0 and 1 can be represented as an int, the values 0, 1, and 2.2 require a float64, and true, 3.3, and "name" require and interface{}.

License

BSD

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	WellKnownAbbreviations = map[string]bool{
		"API":   true,
		"DB":    true,
		"HTTP":  true,
		"HTTPS": true,
		"ID":    true,
		"JSON":  true,
		"OS":    true,
		"SQL":   true,
		"SSH":   true,
		"URI":   true,
		"URL":   true,
		"XML":   true,
		"YAML":  true,
	}
)

Functions

func SplitComponents

func SplitComponents(name string) []string

SplitComponents splits name into components. name may be kebab case, snake case, or camel case.

Types

type AbbreviationHandlingFieldNamer

type AbbreviationHandlingFieldNamer struct {
	Abbreviations map[string]bool
}

An AbbreviationHandlingFieldNamer generates Go field names from JSON properties while keeping abbreviations uppercased.

func (*AbbreviationHandlingFieldNamer) FieldName

func (a *AbbreviationHandlingFieldNamer) FieldName(property string) string

FieldName implements FieldNamer.FieldName.

type FieldNamer

type FieldNamer interface {
	FieldName(property string) string
}

A FieldNamer generates a Go field name from a JSON property.

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

A Generator generates Go types from ObservedValues.

func NewGenerator

func NewGenerator(options ...GeneratorOption) *Generator

NewGenerator returns a new Generator with options.

func (*Generator) GoCode

func (g *Generator) GoCode(observedValue *ObservedValue) ([]byte, error)

GoCode returns the Go source code for o.

func (*Generator) GoType

func (g *Generator) GoType(o *ObservedValue, observations int, imports map[string]bool) (string, bool)

GoType returns the Go type for o and whether it has been omitted.

type GeneratorOption

type GeneratorOption func(*Generator)

A GeneratorOption sets an option on a Generator.

func WithAddStructTagName

func WithAddStructTagName(structTagName string) GeneratorOption

WithAddStructTagName adds a struct tag name.

func WithFieldNamer

func WithFieldNamer(fieldNamer FieldNamer) GeneratorOption

WithFieldNamer sets the fieldNamer.

func WithGoFormat

func WithGoFormat(goFormat bool) GeneratorOption

WithGoFormat sets whether the output is should be formatted with go fmt.

func WithImports

func WithImports(imports ...string) GeneratorOption

WithImports adds custom package imports.

func WithIntType

func WithIntType(intType string) GeneratorOption

WithIntType sets the integer type.

func WithOmitEmpty

func WithOmitEmpty(omitEmptyOption OmitEmptyOption) GeneratorOption

WithOmitEmpty sets whether each field is tagged with omitempty.

func WithPackageComment

func WithPackageComment(packageComment string) GeneratorOption

WithPackageComment sets the package comment.

func WithPackageName

func WithPackageName(packageName string) GeneratorOption

WithPackageName sets the package name.

func WithSkipUnparseableProperties

func WithSkipUnparseableProperties(skipUnparseableProperties bool) GeneratorOption

WithSkipUnparseableProperties sets whether unparseable properties should be skipped.

func WithStructTagName

func WithStructTagName(structTagName string) GeneratorOption

WithStructTagName sets the struct tag name.

func WithStructTagNames

func WithStructTagNames(structTagNames []string) GeneratorOption

WithStructTagNames sets the struct tag names.

func WithTypeComment

func WithTypeComment(typeComment string) GeneratorOption

WithTypeComment sets the type comment.

func WithTypeName

func WithTypeName(typeName string) GeneratorOption

WithTypeName sets the type name.

func WithUseInline

func WithUseInline(useInline bool) GeneratorOption

WithUseInline sets whether to output structs should be inline

func WithUseJSONNumber

func WithUseJSONNumber(useJSONNumber bool) GeneratorOption

WithUseJSONNumber sets whether to use json.Number when both int and float64s are observed for the same property.

type ObservedValue

type ObservedValue struct {
	Observations            int
	Empty                   int
	Array                   int
	Bool                    int
	Float64                 int
	Int                     int
	Null                    int
	Object                  int
	String                  int
	Time                    int // time.Time is an implicit more specific type than string.
	AllArrayElementValues   *ObservedValue
	AllObjectPropertyValues *ObservedValue
	ObjectPropertyValue     map[string]*ObservedValue
	FieldKey                string
}

An ObservedValue is an observed value.

func ObserveJSON

func ObserveJSON(r io.Reader) (*ObservedValue, error)

ObserveJSON returns all JSON values observed in r.

func ObserveYAML

func ObserveYAML(r io.Reader) (*ObservedValue, error)

ObserveYAML returns all YAML values observed in r.

func (*ObservedValue) Merge

func (o *ObservedValue) Merge(value interface{}) *ObservedValue

Merge merges value into o.

type OmitEmptyOption

type OmitEmptyOption int

An OmitEmptyOption is an option for handling omitempty.

const (
	OmitEmptyNever OmitEmptyOption = iota
	OmitEmptyAlways
	OmitEmptyAuto
)

omitempty options.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL