Skip to content
41 changes: 38 additions & 3 deletions jsonhal.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"reflect"
"time"

"github.com/mitchellh/mapstructure"
)
Expand Down Expand Up @@ -44,6 +45,7 @@ type Embedder interface {
type Hal struct {
Links map[string]*Link `json:"_links,omitempty"`
Embedded map[string]Embedded `json:"_embedded,omitempty"`
decoder *mapstructure.Decoder
}

// SetLink sets a link (self, next, etc). Title argument is optional
Expand Down Expand Up @@ -105,13 +107,46 @@ func (h *Hal) CountEmbedded(name string) (int, error) {
return reflect.ValueOf(interface{}(e)).Len(), nil
}

// decodeHook is used to support datatypes that mapstructure does not support native
func (h *Hal) decodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {

// only if target datatype is time.Time and if source datatype is string
if t == reflect.TypeOf(time.Time{}) && f == reflect.TypeOf("") {
return time.Parse(time.RFC3339, data.(string))
}

//everything else would not be handled for now
return data, nil
}

// DecodeEmbedded decodes embedded objects into a struct
func (h *Hal) DecodeEmbedded(name string, result interface{}) error {
func (h *Hal) DecodeEmbedded(name string, result interface{}) (err error) {
var dec *mapstructure.Decoder
defer func() {
if r := recover(); r != nil {
err = r.(error)

}
}()

e, err := h.GetEmbedded(name)
if err != nil {
return err
panic(err)
}
//setup a new decoder if not already present
if h.decoder == nil {
dec, err = mapstructure.NewDecoder(&mapstructure.DecoderConfig{Result: result, DecodeHook: h.decodeHook})
if err != nil {
panic(err)
}
h.decoder = dec
}

err = h.decoder.Decode(e)
if err != nil {
panic(err)
}
return mapstructure.Decode(interface{}(e), result)
return nil
}

// DeleteEmbedded removes an embedded resource named name if it is found
Expand Down
30 changes: 22 additions & 8 deletions jsonhal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"log"
"reflect"
"testing"
"time"

"github.com/RichardKnop/jsonhal"

"github.com/stretchr/testify/assert"
)

Expand All @@ -21,8 +23,9 @@ type HelloWorld struct {
// Foobar is a simple test struct
type Foobar struct {
jsonhal.Hal
ID uint `json:"id"`
Name string `json:"name"`
ID uint `json:"id"`
Name string `json:"name"`
Date time.Time `json:"date"`
}

// Qux is a simple test struct
Expand Down Expand Up @@ -67,7 +70,8 @@ var expectedJSON3 = []byte(`{
}
},
"id": 1,
"name": "Foo bar 1"
"name": "Foo bar 1",
"date":"2017-09-12T08:45:20Z"
}
},
"id": 1,
Expand All @@ -89,7 +93,8 @@ var expectedJSON4 = []byte(`{
}
},
"id": 1,
"name": "Foo bar 1"
"name": "Foo bar 1",
"date":"2017-09-12T08:45:20Z"
},
{
"_links": {
Expand All @@ -98,7 +103,8 @@ var expectedJSON4 = []byte(`{
}
},
"id": 2,
"name": "Foo bar 2"
"name": "Foo bar 2",
"date":"2017-09-12T08:45:20Z"
}
]
},
Expand All @@ -121,7 +127,8 @@ var expectedJSON5 = []byte(`{
}
},
"id": 1,
"name": "Foo bar 1"
"name": "Foo bar 1",
"date":"2017-09-12T08:45:20Z"
},
{
"_links": {
Expand All @@ -130,7 +137,8 @@ var expectedJSON5 = []byte(`{
}
},
"id": 2,
"name": "Foo bar 2"
"name": "Foo bar 2",
"date":"2017-09-12T08:45:20Z"
}
],
"quxes": [
Expand Down Expand Up @@ -204,6 +212,7 @@ func TestHal(t *testing.T) {
assert.Equal(t, expected.String(), string(actual))

// Let's add more links and a single embedded resource
date, _ := time.Parse(time.RFC3339, "2017-09-12T08:45:20Z")
helloWorld = &HelloWorld{ID: 1, Name: "Hello World"}
helloWorld.SetLink(
"self", // name
Expand All @@ -220,7 +229,7 @@ func TestHal(t *testing.T) {
"/v1/hello/world?offset=0&limit=2", // href
"", // title
)
foobar = &Foobar{ID: 1, Name: "Foo bar 1"}
foobar = &Foobar{ID: 1, Name: "Foo bar 1", Date: date}
foobar.SetLink("self", "/v1/foo/bar/1", "")
helloWorld.SetEmbedded("foobar", jsonhal.Embedded(foobar))

Expand Down Expand Up @@ -254,6 +263,7 @@ func TestHal(t *testing.T) {
},
ID: 1,
Name: "Foo bar 1",
Date: date,
},
{
Hal: jsonhal.Hal{
Expand All @@ -263,6 +273,7 @@ func TestHal(t *testing.T) {
},
ID: 2,
Name: "Foo bar 2",
Date: date,
},
}
helloWorld.SetEmbedded("foobars", jsonhal.Embedded(foobars))
Expand Down Expand Up @@ -297,6 +308,7 @@ func TestHal(t *testing.T) {
},
ID: 1,
Name: "Foo bar 1",
Date: date,
},
{
Hal: jsonhal.Hal{
Expand All @@ -306,6 +318,7 @@ func TestHal(t *testing.T) {
},
ID: 2,
Name: "Foo bar 2",
Date: date,
},
}
helloWorld.SetEmbedded("foobars", jsonhal.Embedded(foobars))
Expand Down Expand Up @@ -491,6 +504,7 @@ func TestUnmarshalingAndDecodeEmbedded(t *testing.T) {
assert.NoError(t, hw.DecodeEmbedded("foobar", f))
assert.Equal(t, uint(1), f.ID)
assert.Equal(t, "Foo bar 1", f.Name)
assert.Equal(t, "2017-09-12T08:45:20Z", f.Date.Format(time.RFC3339))

// Slice of embedded objects

Expand Down