diff --git a/id/id.go b/id/id.go
index 6739f1e39f8558a0669aabc61fe40d8dfce60221..8914f87eed12d4329fe6e75f1c54e202ecbdaf12 100644
--- a/id/id.go
+++ b/id/id.go
@@ -15,6 +15,7 @@ import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
+ "encoding/json"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"io"
@@ -112,6 +113,30 @@ func (id *ID) SetType(idType Type) {
id[ArrIDLen-1] = byte(idType)
}
+// UnmarshalJSON is part of the json.Unmarshaler interface and allows IDs to be
+// marshaled into JSON.
+func (id *ID) UnmarshalJSON(b []byte) error {
+ var buff []byte
+ if err := json.Unmarshal(b, &buff); err != nil {
+ return err
+ }
+
+ newID, err := Unmarshal(buff)
+ if err != nil {
+ return err
+ }
+
+ *id = *newID
+
+ return nil
+}
+
+// MarshalJSON is part of the json.Marshaler interface and allows IDs to be
+// marshaled into JSON.
+func (id ID) MarshalJSON() ([]byte, error) {
+ return json.Marshal(id.Marshal())
+}
+
// NewRandomID generates a random ID using the passed in io.Reader r
// and sets the ID to Type t. If the base64 string of the generated
// ID does not begin with an alphanumeric character, then another ID
diff --git a/id/id_test.go b/id/id_test.go
index 71cb079b8932b3014a13b20bcecb9527b8c2a6f7..5d48d23c97ca48065cdd1d36a6a53f17e388fdc1 100644
--- a/id/id_test.go
+++ b/id/id_test.go
@@ -10,6 +10,7 @@ import (
"bytes"
"encoding/base64"
"encoding/binary"
+ "encoding/json"
"fmt"
"math/rand"
"reflect"
@@ -301,6 +302,50 @@ func TestID_SetType_NilError(t *testing.T) {
id.SetType(Generic)
}
+// Tests that an ID can be JSON marshaled and unmarshalled.
+func TestID_MarshalJSON_UnmarshalJSON(t *testing.T) {
+ testID := NewIdFromBytes(rngBytes(ArrIDLen, 42, t), t)
+
+ jsonData, err := json.Marshal(testID)
+ if err != nil {
+ t.Errorf("json.Marshal returned an error: %+v", err)
+ }
+
+ newID := &ID{}
+ err = json.Unmarshal(jsonData, newID)
+ if err != nil {
+ t.Errorf("json.Unmarshal returned an error: %+v", err)
+ }
+
+ if *testID != *newID {
+ t.Errorf("Failed the JSON marshal and unmarshal ID."+
+ "\noriginal ID: %s\nreceived ID: %s", testID, newID)
+ }
+}
+
+// Error path: supplied data is invalid JSON.
+func TestID_UnmarshalJSON_JsonUnmarshalError(t *testing.T) {
+ expectedErr := "invalid character"
+ id := ID{}
+ err := id.UnmarshalJSON([]byte("invalid JSON"))
+ if err == nil || !strings.Contains(err.Error(), expectedErr) {
+ t.Errorf("UnmarshalJSON failed to return the expected error for "+
+ "invalid JSON.\nexpected: %s\nreceived: %+v", expectedErr, err)
+ }
+}
+
+// Error path: supplied data is valid JSON but an invalid ID.
+func TestID_UnmarshalJSON_IdUnmarshalError(t *testing.T) {
+ expectedErr := fmt.Sprintf("Failed to unmarshal ID: length of data "+
+ "must be %d, length received is %d", ArrIDLen, 0)
+ id := ID{}
+ err := id.UnmarshalJSON([]byte("\"\""))
+ if err == nil || !strings.Contains(err.Error(), expectedErr) {
+ t.Errorf("UnmarshalJSON failed to return the expected error for "+
+ "invalid ID.\nexpected: %s\nreceived: %+v", expectedErr, err)
+ }
+}
+
// Tests that NewRandomID returns the expected IDs for a given PRNG.
func TestNewRandomID_Consistency(t *testing.T) {
prng := rand.New(rand.NewSource(42))
@@ -599,3 +644,19 @@ func (prng *alphaNumericPRNG) Read(p []byte) (n int, err error) {
return len(p), nil
}
}
+
+// Generates a byte slice of the specified length containing random numbers.
+func rngBytes(length int, seed int64, t *testing.T) []byte {
+ prng := rand.New(rand.NewSource(seed))
+
+ // Create new byte slice of the correct size
+ idBytes := make([]byte, length)
+
+ // Create random bytes
+ _, err := prng.Read(idBytes)
+ if err != nil {
+ t.Fatalf("Failed to generate random bytes: %+v", err)
+ }
+
+ return idBytes
+}