Skip to content

Commit

Permalink
Marshal and Unmarshal for structs (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
trobro authored Jul 23, 2022
1 parent fed36c4 commit ea903d2
Show file tree
Hide file tree
Showing 8 changed files with 1,293 additions and 213 deletions.
100 changes: 77 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Build Status](https://github.com/hjson/hjson-go/workflows/test/badge.svg)](https://github.com/hjson/hjson-go/actions)
[![Go Pkg](https://img.shields.io/github/release/hjson/hjson-go.svg?style=flat-square&label=go-pkg)](https://github.com/hjson/hjson-go/releases)
[![Go Report Card](https://goreportcard.com/badge/github.com/hjson/hjson-go?style=flat-square)](https://goreportcard.com/report/github.com/hjson/hjson-go)
[![coverage](https://img.shields.io/badge/coverage-ok-brightgreen.svg?style=flat-square)](http://gocover.io/github.com/hjson/hjson-go/)
[![coverage](https://img.shields.io/badge/coverage-ok-brightgreen.svg?style=flat-square)](https://gocover.io/github.com/hjson/hjson-go/)
[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/hjson/hjson-go/v4)

![Hjson Intro](https://hjson.github.io/hjson1.gif)
Expand All @@ -29,9 +29,11 @@

The Go implementation of Hjson is based on [hjson-js](https://github.com/hjson/hjson-js). For other platforms see [hjson.github.io](https://hjson.github.io).

More documentation can be found at https://pkg.go.dev/github.com/hjson/hjson-go/v4

# Install

Make sure you have a working Go environment. See the [install instructions](http://golang.org/doc/install.html).
Make sure you have a working Go environment. See the [install instructions](https://golang.org/doc/install.html).

- In order to use Hjson from your own Go source code, just add an import line like the one here below. Before building your project, run `go mod tidy` in order to download the Hjson source files. The suffix `/v4` is required in the import path, unless you specifically want to use an older major version.
```go
Expand All @@ -49,8 +51,6 @@ hjson can be used to convert JSON from/to Hjson.
hjson will read the given JSON/Hjson input file or read from stdin.
Options:
-allowMinusZero
Allow -0.
-bracesSameLine
Print braces on the same line.
-c Output as JSON.
Expand All @@ -75,12 +75,11 @@ Sample:
package main

import (
"github.com/hjson/hjson-go/v4"
"fmt"
"github.com/hjson/hjson-go/v4"
"fmt"
)

func main() {

// Now let's look at decoding Hjson data into Go
// values.
sampleText := []byte(`
Expand All @@ -98,10 +97,13 @@ func main() {
// can put the decoded data.
var dat map[string]interface{}

// Decode and a check for errors.
// Decode with default options and check for errors.
if err := hjson.Unmarshal(sampleText, &dat); err != nil {
panic(err)
}
// short for:
// options := hjson.DefaultDecoderOptions()
// err := hjson.UnmarshalWithOptions(sampleText, &dat, options)
fmt.Println(dat)

// In order to use the values in the decoded map,
Expand All @@ -118,32 +120,37 @@ func main() {
// To encode to Hjson with default options:
sampleMap := map[string]int{"apple": 5, "lettuce": 7}
hjson, _ := hjson.Marshal(sampleMap)
// this is short for:
// short for:
// options := hjson.DefaultOptions()
// hjson, _ := hjson.MarshalWithOptions(sampleMap, options)
fmt.Println(string(hjson))
}
```

If you prefer, you can also unmarshal to Go objects by converting to JSON:
## Unmarshal to Go structs

If you prefer, you can also unmarshal to Go structs (including structs implementing the json.Unmarshaler interface or the encoding.TextUnmarshaler interface). The Go JSON package is used for this, so the same rules apply. Specifically for the "json" key in struct field tags. For more details about this type of unmarshalling, see the [documentation for json.Unmarshal()](https://pkg.go.dev/encoding/json#Unmarshal).

```go

package main

import (
"github.com/hjson/hjson-go/v4"
"encoding/json"
"fmt"
"github.com/hjson/hjson-go/v4"
"fmt"
)

type Sample struct {
Rate int
Array []string
}

func main() {
type SampleAlias struct {
Rett int `json:"rate"`
Ashtray []string `json:"array"`
}

func main() {
sampleText := []byte(`
{
# specify rate in requests/second
Expand All @@ -155,25 +162,72 @@ func main() {
]
}`)

// read Hjson
var dat map[string]interface{}
hjson.Unmarshal(sampleText, &dat)

// convert to JSON
b, _ := json.Marshal(dat)

// unmarshal
var sample Sample
json.Unmarshal(b, &sample)
hjson.Unmarshal(sampleText, &sample)

fmt.Println(sample.Rate)
fmt.Println(sample.Array)

// unmarshal using json tags on struct fields
var sampleAlias SampleAlias
hjson.Unmarshal(sampleText, &sampleAlias)

fmt.Println(sampleAlias.Rett)
fmt.Println(sampleAlias.Ashtray)
}
```

## Comments on struct fields

By using key `comment` in struct field tags you can specify comments to be written on one or more lines preceding the struct field in the Hjson output.

```go

package main

import (
"github.com/hjson/hjson-go/v4"
"fmt"
)

type foo struct {
A string `json:"x" comment:"First comment"`
B int32 `comment:"Second comment\nLook ma, new lines"`
C string
D int32
}

func main() {
a := foo{A: "hi!", B: 3, C: "some text", D: 5}
buf, err := hjson.Marshal(a)
if err != nil {
fmt.Error(err)
}

fmt.Println(string(buf))
}
```

Output:

```
{
# First comment
x: hi!
# Second comment
# Look ma, new lines
B: 3
C: some text
D: 5
}
```

# API

[![godoc](https://godoc.org/github.com/hjson/hjson-go/v4?status.svg)](http://godoc.org/github.com/hjson/hjson-go/v4)
[![godoc](https://godoc.org/github.com/hjson/hjson-go/v4?status.svg)](https://godoc.org/github.com/hjson/hjson-go/v4)

# History

Expand Down
89 changes: 70 additions & 19 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,34 @@ package hjson

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
)

// DecoderOptions defines options for decoding Hjson.
type DecoderOptions struct {
// UseJSONNumber causes the Decoder to unmarshal a number into an interface{} as a
// json.Number instead of as a float64.
UseJSONNumber bool
// DisallowUnknownFields causes an error to be returned when the destination
// is a struct and the input contains object keys which do not match any
// non-ignored, exported fields in the destination.
DisallowUnknownFields bool
}

// DefaultDecoderOptions returns the default decoding options.
func DefaultDecoderOptions() DecoderOptions {
return DecoderOptions{
UseJSONNumber: false,
DisallowUnknownFields: false,
}
}

type hjsonParser struct {
DecoderOptions
data []byte
at int // The index of the current character
ch byte // The current character
Expand Down Expand Up @@ -294,7 +316,8 @@ func (p *hjsonParser) readTfnns() (interface{}, error) {
}
default:
if chf == '-' || chf >= '0' && chf <= '9' {
if n, err := tryParseNumber(value.Bytes(), false); err == nil {
// Always use json.Number because we will marshal to JSON.
if n, err := tryParseNumber(value.Bytes(), false, true); err == nil {
return n, nil
}
}
Expand Down Expand Up @@ -448,33 +471,61 @@ func (p *hjsonParser) checkTrailing(v interface{}, err error) (interface{}, erro
return v, nil
}

// Unmarshal parses the Hjson-encoded data and stores the result
// Unmarshal parses the Hjson-encoded data using default options and stores the
// result in the value pointed to by v.
//
// See UnmarshalWithOptions.
//
func Unmarshal(data []byte, v interface{}) error {
return UnmarshalWithOptions(data, v, DefaultDecoderOptions())
}

// UnmarshalWithOptions parses the Hjson-encoded data and stores the result
// in the value pointed to by v.
//
// Unmarshal uses the inverse of the encodings that
// Marshal uses, allocating maps, slices, and pointers as necessary.
// Internally the Hjson input is converted to JSON, which is then used as input
// to the function json.Unmarshal().
//
func Unmarshal(data []byte, v interface{}) (err error) {
var value interface{}
parser := &hjsonParser{data, 0, ' '}
// For more details about the output from this function, see the documentation
// for json.Unmarshal().
func UnmarshalWithOptions(data []byte, v interface{}, options DecoderOptions) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return fmt.Errorf("Cannot unmarshal into non-pointer %v", reflect.TypeOf(v))
}

parser := &hjsonParser{
DecoderOptions: options,
data: data,
at: 0,
ch: ' ',
}
parser.resetAt()
value, err = parser.rootValue()
value, err := parser.rootValue()
if err != nil {
return err
}

rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return fmt.Errorf("non-pointer %v", reflect.TypeOf(v))
// Convert to JSON so we can let json.Unmarshal() handle all destination
// types (including interfaces json.Unmarshaler and encoding.TextUnmarshaler)
// and merging.
buf, err := json.Marshal(value)
if err != nil {
return errors.New("Internal error")
}
for rv.Kind() == reflect.Ptr {
rv = rv.Elem()

dec := json.NewDecoder(bytes.NewBuffer(buf))
if parser.UseJSONNumber {
dec.UseNumber()
}
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}()
rv.Set(reflect.ValueOf(value))
if parser.DisallowUnknownFields {
dec.DisallowUnknownFields()
}

err = dec.Decode(v)
if err != nil {
return err
}

return err
}
Loading

0 comments on commit ea903d2

Please sign in to comment.