commit
0ef05ba2c2
74
README.md
74
README.md
@ -1,2 +1,74 @@
|
|||||||
# jsonenums
|
# jsonenums
|
||||||
This tool is similar to golang.org/x/tools/cmd/stringer but generates MarshalJSON and UnmarshalJSON methods.
|
|
||||||
|
jsonenums is a tool to automate the creation of methods that satisfy the
|
||||||
|
`json.Marshaler` and `json.Unmarshaler` interfaces.
|
||||||
|
Given the name of a (signed or unsigned) integer type T that has constants
|
||||||
|
defined, jsonenums will create a new self-contained Go source file implementing
|
||||||
|
|
||||||
|
```
|
||||||
|
func (t T) MarshalJSON() ([]byte, error)
|
||||||
|
func (t *T) UnmarshalJSON([]byte) error
|
||||||
|
```
|
||||||
|
|
||||||
|
The file is created in the same package and directory as the package that
|
||||||
|
defines T. It has helpful defaults designed for use with go generate.
|
||||||
|
|
||||||
|
jsonenums is a simple implementation of a concept and the code might not be the
|
||||||
|
most performant or beautiful to read.
|
||||||
|
|
||||||
|
For example, given this snippet,
|
||||||
|
|
||||||
|
```
|
||||||
|
package painkiller
|
||||||
|
|
||||||
|
type Pill int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Placebo Pill = iota
|
||||||
|
Aspirin
|
||||||
|
Ibuprofen
|
||||||
|
Paracetamol
|
||||||
|
Acetaminophen = Paracetamol
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
running this command
|
||||||
|
|
||||||
|
```
|
||||||
|
jsonenums -type=Pill
|
||||||
|
```
|
||||||
|
|
||||||
|
in the same directory will create the file `pill_jsonenums.go`, in package
|
||||||
|
`painkiller`, containing a definition of
|
||||||
|
|
||||||
|
```
|
||||||
|
func (r Pill) MarshalJSON() ([]byte, error)
|
||||||
|
func (r *Pill) UnmarshalJSON([]byte) error
|
||||||
|
```
|
||||||
|
|
||||||
|
`MarshalJSON` will translate the value of a `Pill` constant to the `[]byte`
|
||||||
|
representation of the respective constant name, so that the call
|
||||||
|
`json.Marshal(painkiller.Aspirin) will return the bytes `[]byte("\"Aspirin\"")`.
|
||||||
|
|
||||||
|
`UnmarshalJSON` performs the opposite operation; given the `[]byte`
|
||||||
|
representation of a `Pill` constant it will change the receiver to equal the
|
||||||
|
corresponding constant. So given `[]byte("\"Aspirin\"")` the receiver will
|
||||||
|
change to `Aspirin` and the returned error will be `nil`.
|
||||||
|
|
||||||
|
Typically this process would be run using go generate, like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
//go:generate jsonenums -type=Pill
|
||||||
|
```
|
||||||
|
|
||||||
|
If multiple constants have the same value, the lexically first matching name
|
||||||
|
will be used (in the example, Acetaminophen will print as "Paracetamol").
|
||||||
|
|
||||||
|
With no arguments, it processes the package in the current directory. Otherwise,
|
||||||
|
the arguments must name a single directory holding a Go package or a set of Go
|
||||||
|
source files that represent a single Go package.
|
||||||
|
|
||||||
|
The `-type` flag accepts a comma-separated list of types so a single run can
|
||||||
|
generate methods for multiple types. The default output file is t_jsonenums.go,
|
||||||
|
where t is the lower-cased name of the first type listed. THe suffix can be
|
||||||
|
overridden with the `-suffix` flag.
|
||||||
|
77
example/shirtsize.go
Normal file
77
example/shirtsize.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate jsonenums -type=ShirtSize
|
||||||
|
|
||||||
|
type ShirtSize byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
NA ShirtSize = iota
|
||||||
|
XS
|
||||||
|
S
|
||||||
|
M
|
||||||
|
L
|
||||||
|
XL
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate jsonenums -type=WeekDay
|
||||||
|
|
||||||
|
type WeekDay int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Monday WeekDay = iota
|
||||||
|
Tuesday
|
||||||
|
Wednesday
|
||||||
|
Thursday
|
||||||
|
Friday
|
||||||
|
Saturday
|
||||||
|
Sunday
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d WeekDay) String() string {
|
||||||
|
switch d {
|
||||||
|
case Monday:
|
||||||
|
return "Dilluns"
|
||||||
|
case Tuesday:
|
||||||
|
return "Dimarts"
|
||||||
|
case Wednesday:
|
||||||
|
return "Dimecres"
|
||||||
|
case Thursday:
|
||||||
|
return "Dijous"
|
||||||
|
case Friday:
|
||||||
|
return "Divendres"
|
||||||
|
case Saturday:
|
||||||
|
return "Dissabte"
|
||||||
|
case Sunday:
|
||||||
|
return "Diumenge"
|
||||||
|
default:
|
||||||
|
return "invalid WeekDay"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := struct {
|
||||||
|
Size ShirtSize
|
||||||
|
Day WeekDay
|
||||||
|
}{M, Friday}
|
||||||
|
if err := json.NewEncoder(os.Stdout).Encode(v); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := `{"Size":"XL", "Day":"Dimarts"}`
|
||||||
|
if err := json.NewDecoder(strings.NewReader(input)).Decode(&v); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("decoded %s as %+v\n", input, v)
|
||||||
|
}
|
47
example/shirtsize_jsonenums.go
Normal file
47
example/shirtsize_jsonenums.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// generated by jsonenums -type=ShirtSize; DO NOT EDIT
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r ShirtSize) MarshalJSON() ([]byte, error) {
|
||||||
|
if s, ok := interface{}(r).(fmt.Stringer); ok {
|
||||||
|
return json.Marshal(s.String())
|
||||||
|
}
|
||||||
|
s, ok := map[ShirtSize]string{
|
||||||
|
NA: "NA", XS: "XS", S: "S", M: "M", L: "L", XL: "XL",
|
||||||
|
}[r]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid ShirtSize: %d", r)
|
||||||
|
}
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ShirtSizeNameToValue = map[string]ShirtSize{
|
||||||
|
"NA": NA, "XS": XS, "S": S, "M": M, "L": L, "XL": XL,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var v ShirtSize
|
||||||
|
if _, ok := interface{}(v).(fmt.Stringer); ok {
|
||||||
|
_ShirtSizeNameToValue = map[string]ShirtSize{
|
||||||
|
interface{}(NA).(fmt.Stringer).String(): NA, interface{}(XS).(fmt.Stringer).String(): XS, interface{}(S).(fmt.Stringer).String(): S, interface{}(M).(fmt.Stringer).String(): M, interface{}(L).(fmt.Stringer).String(): L, interface{}(XL).(fmt.Stringer).String(): XL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ShirtSize) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return fmt.Errorf("ShirtSize should be a string, got %s", data)
|
||||||
|
}
|
||||||
|
v, ok := _ShirtSizeNameToValue[s]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid ShirtSize %q", s)
|
||||||
|
}
|
||||||
|
*r = v
|
||||||
|
return nil
|
||||||
|
}
|
47
example/weekday_jsonenums.go
Normal file
47
example/weekday_jsonenums.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// generated by jsonenums -type=WeekDay; DO NOT EDIT
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r WeekDay) MarshalJSON() ([]byte, error) {
|
||||||
|
if s, ok := interface{}(r).(fmt.Stringer); ok {
|
||||||
|
return json.Marshal(s.String())
|
||||||
|
}
|
||||||
|
s, ok := map[WeekDay]string{
|
||||||
|
Monday: "Monday", Tuesday: "Tuesday", Wednesday: "Wednesday", Thursday: "Thursday", Friday: "Friday", Saturday: "Saturday", Sunday: "Sunday",
|
||||||
|
}[r]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid WeekDay: %d", r)
|
||||||
|
}
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _WeekDayNameToValue = map[string]WeekDay{
|
||||||
|
"Monday": Monday, "Tuesday": Tuesday, "Wednesday": Wednesday, "Thursday": Thursday, "Friday": Friday, "Saturday": Saturday, "Sunday": Sunday,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var v WeekDay
|
||||||
|
if _, ok := interface{}(v).(fmt.Stringer); ok {
|
||||||
|
_WeekDayNameToValue = map[string]WeekDay{
|
||||||
|
interface{}(Monday).(fmt.Stringer).String(): Monday, interface{}(Tuesday).(fmt.Stringer).String(): Tuesday, interface{}(Wednesday).(fmt.Stringer).String(): Wednesday, interface{}(Thursday).(fmt.Stringer).String(): Thursday, interface{}(Friday).(fmt.Stringer).String(): Friday, interface{}(Saturday).(fmt.Stringer).String(): Saturday, interface{}(Sunday).(fmt.Stringer).String(): Sunday,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *WeekDay) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return fmt.Errorf("WeekDay should be a string, got %s", data)
|
||||||
|
}
|
||||||
|
v, ok := _WeekDayNameToValue[s]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid WeekDay %q", s)
|
||||||
|
}
|
||||||
|
*r = v
|
||||||
|
return nil
|
||||||
|
}
|
286
jsonenums.go
Normal file
286
jsonenums.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// JSONenums is a tool to automate the creation of methods that satisfy the
|
||||||
|
// fmt.Stringer, json.Marshaler and json.Unmarshaler interfaces.
|
||||||
|
// Given the name of a (signed or unsigned) integer type T that has constants
|
||||||
|
// defined, stringer will create a new self-contained Go source file implementing
|
||||||
|
//
|
||||||
|
// func (t T) String() string
|
||||||
|
// func (t T) MarshalJSON() ([]byte, error)
|
||||||
|
// func (t *T) UnmarshalJSON([]byte) error
|
||||||
|
//
|
||||||
|
// The file is created in the same package and directory as the package that defines T.
|
||||||
|
// It has helpful defaults designed for use with go generate.
|
||||||
|
//
|
||||||
|
// JSONenums is a simple implementation of a concept and the code might not be
|
||||||
|
// the most performant or beautiful to read.
|
||||||
|
//
|
||||||
|
// For example, given this snippet,
|
||||||
|
//
|
||||||
|
// package painkiller
|
||||||
|
//
|
||||||
|
// type Pill int
|
||||||
|
//
|
||||||
|
// const (
|
||||||
|
// Placebo Pill = iota
|
||||||
|
// Aspirin
|
||||||
|
// Ibuprofen
|
||||||
|
// Paracetamol
|
||||||
|
// Acetaminophen = Paracetamol
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// running this command
|
||||||
|
//
|
||||||
|
// jsonenums -type=Pill
|
||||||
|
//
|
||||||
|
// in the same directory will create the file pill_jsonenums.go, in package painkiller,
|
||||||
|
// containing a definition of
|
||||||
|
//
|
||||||
|
// func (r Pill) String() string
|
||||||
|
// func (r Pill) MarshalJSON() ([]byte, error)
|
||||||
|
// func (r *Pill) UnmarshalJSON([]byte) error
|
||||||
|
//
|
||||||
|
// That method will translate the value of a Pill constant to the string representation
|
||||||
|
// of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will
|
||||||
|
// print the string "Aspirin".
|
||||||
|
//
|
||||||
|
// Typically this process would be run using go generate, like this:
|
||||||
|
//
|
||||||
|
// //go:generate stringer -type=Pill
|
||||||
|
//
|
||||||
|
// If multiple constants have the same value, the lexically first matching name will
|
||||||
|
// be used (in the example, Acetaminophen will print as "Paracetamol").
|
||||||
|
//
|
||||||
|
// With no arguments, it processes the package in the current directory.
|
||||||
|
// Otherwise, the arguments must name a single directory holding a Go package
|
||||||
|
// or a set of Go source files that represent a single Go package.
|
||||||
|
//
|
||||||
|
// The -type flag accepts a comma-separated list of types so a single run can
|
||||||
|
// generate methods for multiple types. The default output file is t_string.go,
|
||||||
|
// where t is the lower-cased name of the first type listed. THe suffix can be
|
||||||
|
// overridden with the -suffix flag.
|
||||||
|
//
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/format"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/exact"
|
||||||
|
"golang.org/x/tools/go/types"
|
||||||
|
|
||||||
|
_ "golang.org/x/tools/go/gcimporter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
|
||||||
|
outputSuffix = flag.String("suffix", "_jsonenums", "suffix to be added to the output file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if len(*typeNames) == 0 {
|
||||||
|
log.Fatalf("the flag -type must be set")
|
||||||
|
}
|
||||||
|
types := strings.Split(*typeNames, ",")
|
||||||
|
|
||||||
|
// Only one directory at a time can be processed, and the default is ".".
|
||||||
|
dir := "."
|
||||||
|
if args := flag.Args(); len(args) == 1 {
|
||||||
|
dir = args[0]
|
||||||
|
} else if len(args) > 1 {
|
||||||
|
log.Fatalf("only one directory at a time")
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg, err := parsePackage(dir, *outputSuffix+".go")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing package: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var analysis = struct {
|
||||||
|
Command string
|
||||||
|
PackageName string
|
||||||
|
TypesAndValues map[string][]string
|
||||||
|
}{
|
||||||
|
Command: strings.Join(os.Args[1:], " "),
|
||||||
|
PackageName: pkg.name,
|
||||||
|
TypesAndValues: make(map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run generate for each type.
|
||||||
|
for _, typeName := range types {
|
||||||
|
values, err := pkg.valuesOfType(typeName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("finding values for type %v: %v", typeName, err)
|
||||||
|
}
|
||||||
|
analysis.TypesAndValues[typeName] = values
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := generatedTmpl.Execute(&buf, analysis); err != nil {
|
||||||
|
log.Fatalf("generating code: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
// Should never happen, but can arise when developing this code.
|
||||||
|
// The user can compile the output to see the error.
|
||||||
|
log.Printf("warning: internal error: invalid Go generated: %s", err)
|
||||||
|
log.Printf("warning: compile the package to analyze the error")
|
||||||
|
src = buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
output := strings.ToLower(typeName + *outputSuffix + ".go")
|
||||||
|
outputPath := filepath.Join(dir, output)
|
||||||
|
if err := ioutil.WriteFile(outputPath, src, 0644); err != nil {
|
||||||
|
log.Fatalf("writing output: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Package struct {
|
||||||
|
name string
|
||||||
|
files []*ast.File
|
||||||
|
|
||||||
|
defs map[*ast.Ident]types.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePackage parses the package in the given directory and returns it.
|
||||||
|
func parsePackage(directory string, skipSuffix string) (*Package, error) {
|
||||||
|
pkgDir, err := build.Default.ImportDir(directory, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot process directory %s: %s", directory, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var files []*ast.File
|
||||||
|
fs := token.NewFileSet()
|
||||||
|
for _, name := range pkgDir.GoFiles {
|
||||||
|
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, skipSuffix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if directory != "." {
|
||||||
|
name = filepath.Join(directory, name)
|
||||||
|
}
|
||||||
|
f, err := parser.ParseFile(fs, name, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing file %v: %v", name, err)
|
||||||
|
}
|
||||||
|
files = append(files, f)
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return nil, fmt.Errorf("%s: no buildable Go files", directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// type-check the package
|
||||||
|
defs := make(map[*ast.Ident]types.Object)
|
||||||
|
config := types.Config{FakeImportC: true}
|
||||||
|
info := &types.Info{Defs: defs}
|
||||||
|
if _, err := config.Check(directory, fs, files, info); err != nil {
|
||||||
|
return nil, fmt.Errorf("type-checking package: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Package{
|
||||||
|
name: files[0].Name.Name,
|
||||||
|
files: files,
|
||||||
|
defs: defs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate produces the String method for the named type.
|
||||||
|
func (pkg *Package) valuesOfType(typeName string) ([]string, error) {
|
||||||
|
var values, inspectErrs []string
|
||||||
|
for _, file := range pkg.files {
|
||||||
|
ast.Inspect(file, func(node ast.Node) bool {
|
||||||
|
decl, ok := node.(*ast.GenDecl)
|
||||||
|
if !ok || decl.Tok != token.CONST {
|
||||||
|
// We only care about const declarations.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if vs, err := pkg.valuesOfTypeIn(typeName, decl); err != nil {
|
||||||
|
inspectErrs = append(inspectErrs, err.Error())
|
||||||
|
} else {
|
||||||
|
values = append(values, vs...)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(inspectErrs) > 0 {
|
||||||
|
return nil, fmt.Errorf("inspecting code:\n\t%v", strings.Join(inspectErrs, "\n\t"))
|
||||||
|
}
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil, fmt.Errorf("no values defined for type %s", typeName)
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pkg *Package) valuesOfTypeIn(typeName string, decl *ast.GenDecl) ([]string, error) {
|
||||||
|
var values []string
|
||||||
|
|
||||||
|
// The name of the type of the constants we are declaring.
|
||||||
|
// Can change if this is a multi-element declaration.
|
||||||
|
typ := ""
|
||||||
|
// Loop over the elements of the declaration. Each element is a ValueSpec:
|
||||||
|
// a list of names possibly followed by a type, possibly followed by values.
|
||||||
|
// If the type and value are both missing, we carry down the type (and value,
|
||||||
|
// but the "go/types" package takes care of that).
|
||||||
|
for _, spec := range decl.Specs {
|
||||||
|
vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST.
|
||||||
|
if vspec.Type == nil && len(vspec.Values) > 0 {
|
||||||
|
// "X = 1". With no type but a value, the constant is untyped.
|
||||||
|
// Skip this vspec and reset the remembered type.
|
||||||
|
typ = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if vspec.Type != nil {
|
||||||
|
// "X T". We have a type. Remember it.
|
||||||
|
ident, ok := vspec.Type.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typ = ident.Name
|
||||||
|
}
|
||||||
|
if typ != typeName {
|
||||||
|
// This is not the type we're looking for.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now have a list of names (from one line of source code) all being
|
||||||
|
// declared with the desired type.
|
||||||
|
// Grab their names and actual values and store them in f.values.
|
||||||
|
for _, name := range vspec.Names {
|
||||||
|
if name.Name == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// This dance lets the type checker find the values for us. It's a
|
||||||
|
// bit tricky: look up the object declared by the name, find its
|
||||||
|
// types.Const, and extract its value.
|
||||||
|
obj, ok := pkg.defs[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no value for constant %s", name)
|
||||||
|
}
|
||||||
|
info := obj.Type().Underlying().(*types.Basic).Info()
|
||||||
|
if info&types.IsInteger == 0 {
|
||||||
|
return nil, fmt.Errorf("can't handle non-integer constant type %s", typ)
|
||||||
|
}
|
||||||
|
value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST.
|
||||||
|
if value.Kind() != exact.Int {
|
||||||
|
log.Fatalf("can't happen: constant is not an integer %s", name)
|
||||||
|
}
|
||||||
|
values = append(values, name.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
63
template.go
Normal file
63
template.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Added as a .go file to avoid embedding issues of the template.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "text/template"
|
||||||
|
|
||||||
|
var generatedTmpl = template.Must(template.New("generated").Parse(`
|
||||||
|
// generated by jsonenums {{.Command}}; DO NOT EDIT
|
||||||
|
|
||||||
|
package {{.PackageName}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
{{range $typename, $values := .TypesAndValues}}
|
||||||
|
|
||||||
|
func (r {{$typename}}) MarshalJSON() ([]byte, error) {
|
||||||
|
if s, ok := interface{}(r).(fmt.Stringer); ok {
|
||||||
|
return json.Marshal(s.String())
|
||||||
|
}
|
||||||
|
s, ok := map[{{$typename}}]string {
|
||||||
|
{{range $values}}{{.}}: "{{.}}",{{end}}
|
||||||
|
}[r]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid {{$typename}}: %d", r)
|
||||||
|
}
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _{{$typename}}NameToValue = map[string]{{$typename}} {
|
||||||
|
{{range $values}}"{{.}}": {{.}},{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var v {{$typename}}
|
||||||
|
if _, ok := interface{}(v).(fmt.Stringer); ok {
|
||||||
|
_{{$typename}}NameToValue = map[string]{{$typename}} {
|
||||||
|
{{range $values}}interface{}({{.}}).(fmt.Stringer).String(): {{.}},{{end}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *{{$typename}}) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return fmt.Errorf("{{$typename}} should be a string, got %s", data)
|
||||||
|
}
|
||||||
|
v, ok := _{{$typename}}NameToValue[s]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid {{$typename}} %q", s)
|
||||||
|
}
|
||||||
|
*r = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
`))
|
Loading…
Reference in New Issue
Block a user