Browse Source

Custom JSON serialisation

Dmitry Panov 9 years ago
parent
commit
f7e7d2e1e1
3 changed files with 48 additions and 20 deletions
  1. 16 20
      builtin_json.go
  2. 12 0
      object_goreflect.go
  3. 20 0
      runtime_test.go

+ 16 - 20
builtin_json.go

@@ -5,8 +5,8 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"strings"
 	"math"
+	"strings"
 )
 
 var hex = "0123456789abcdef"
@@ -288,15 +288,7 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
 					Arguments: []Value{key},
 				})
 			}
-		} /*else {
-			// If the object is a GoStruct or something that implements json.Marshaler
-			if object.objectClass.marshalJSON != nil {
-				marshaler := object.objectClass.marshalJSON(object)
-				if marshaler != nil {
-					return marshaler, true
-				}
-			}
-		}*/
+		}
 	}
 
 	if ctx.replacerFunction != nil {
@@ -313,16 +305,20 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
 		case *stringObject:
 			value = o1.value
 		case *objectGoReflect:
-			switch o.self.className() {
-			case classNumber:
-				value = o1.toPrimitiveNumber()
-			case classString:
-				value = o1.toPrimitiveString()
-			case classBoolean:
-				if o.ToInteger() != 0 {
-					value = valueTrue
-				} else {
-					value = valueFalse
+			if o1.toJson != nil {
+				value = ctx.r.ToValue(o1.toJson())
+			} else {
+				switch o1.className() {
+				case classNumber:
+					value = o1.toPrimitiveNumber()
+				case classString:
+					value = o1.toPrimitiveString()
+				case classBoolean:
+					if o.ToInteger() != 0 {
+						value = valueTrue
+					} else {
+						value = valueFalse
+					}
 				}
 			}
 		}

+ 12 - 0
object_goreflect.go

@@ -6,6 +6,12 @@ import (
 	"reflect"
 )
 
+// JsonEncodable allows custom JSON encoding by JSON.stringify()
+// Note that if the returned value itself also implements JsonEncodable, it won't have any effect.
+type JsonEncodable interface {
+	JsonEncodable() interface{}
+}
+
 // FieldNameMapper provides custom mapping between Go and JavaScript property names.
 type FieldNameMapper interface {
 	// FieldName returns a JavaScript name for the given struct field in the given type.
@@ -33,6 +39,8 @@ type objectGoReflect struct {
 	origValue, value reflect.Value
 
 	valueTypeInfo, origValueTypeInfo *reflectTypeInfo
+
+	toJson func() interface{}
 }
 
 func (o *objectGoReflect) init() {
@@ -60,6 +68,10 @@ func (o *objectGoReflect) init() {
 
 	o.valueTypeInfo = o.val.runtime.typeInfo(o.value.Type())
 	o.origValueTypeInfo = o.val.runtime.typeInfo(o.origValue.Type())
+
+	if j, ok := o.origValue.Interface().(JsonEncodable); ok {
+		o.toJson = j.JsonEncodable
+	}
 }
 
 func (o *objectGoReflect) toStringFunc(call FunctionCall) Value {

+ 20 - 0
runtime_test.go

@@ -555,6 +555,26 @@ func TestJSONNil(t *testing.T) {
 	}
 }
 
+type customJsonEncodable struct{}
+
+func (*customJsonEncodable) JsonEncodable() interface{} {
+	return "Test"
+}
+
+func TestJsonEncodable(t *testing.T) {
+	var s customJsonEncodable
+
+	vm := New()
+	vm.Set("s", &s)
+
+	ret, err := vm.RunString("JSON.stringify(s)")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !ret.StrictEquals(vm.ToValue("\"Test\"")) {
+		t.Fatalf("Expected \"Test\", got: %v", ret)
+	}
+}
 
 /*
 func TestArrayConcatSparse(t *testing.T) {