package otto import ( "bytes" "encoding/json" "fmt" "strings" ) type _builtinJSON_parseContext struct { call FunctionCall reviver Value } func builtinJSON_parse(call FunctionCall) Value { ctx := _builtinJSON_parseContext{ call: call, } revive := false if reviver := call.Argument(1); reviver.isCallable() { revive = true ctx.reviver = reviver } var root interface{} err := json.Unmarshal([]byte(call.Argument(0).string()), &root) if err != nil { panic(call.runtime.panicSyntaxError(err.Error())) } value, exists := builtinJSON_parseWalk(ctx, root) if !exists { value = Value{} } if revive { root := ctx.call.runtime.newObject() root.put("", value, false) return builtinJSON_reviveWalk(ctx, root, "") } return value } func builtinJSON_reviveWalk(ctx _builtinJSON_parseContext, holder *_object, name string) Value { value := holder.get(name) if object := value._object(); object != nil { if isArray(object) { length := int64(objectLength(object)) for index := int64(0); index < length; index += 1 { name := arrayIndexToString(index) value := builtinJSON_reviveWalk(ctx, object, name) if value.IsUndefined() { object.delete(name, false) } else { object.defineProperty(name, value, 0111, false) } } } else { object.enumerate(false, func(name string) bool { value := builtinJSON_reviveWalk(ctx, object, name) if value.IsUndefined() { object.delete(name, false) } else { object.defineProperty(name, value, 0111, false) } return true }) } } return ctx.reviver.call(ctx.call.runtime, toValue_object(holder), name, value) } func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) { switch value := rawValue.(type) { case nil: return nullValue, true case bool: return toValue_bool(value), true case string: return toValue_string(value), true case float64: return toValue_float64(value), true case []interface{}: arrayValue := make([]Value, len(value)) for index, rawValue := range value { if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists { arrayValue[index] = value } } return toValue_object(ctx.call.runtime.newArrayOf(arrayValue)), true case map[string]interface{}: object := ctx.call.runtime.newObject() for name, rawValue := range value { if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists { object.put(name, value, false) } } return toValue_object(object), true } return Value{}, false } type _builtinJSON_stringifyContext struct { call FunctionCall stack []*_object propertyList []string replacerFunction *Value gap string } func builtinJSON_stringify(call FunctionCall) Value { ctx := _builtinJSON_stringifyContext{ call: call, stack: []*_object{nil}, } replacer := call.Argument(1)._object() if replacer != nil { if isArray(replacer) { length := objectLength(replacer) seen := map[string]bool{} propertyList := make([]string, length) length = 0 for index, _ := range propertyList { value := replacer.get(arrayIndexToString(int64(index))) switch value.kind { case valueObject: switch value.value.(*_object).class { case classString: case classNumber: default: continue } case valueString: case valueNumber: default: continue } name := value.string() if seen[name] { continue } seen[name] = true length += 1 propertyList[index] = name } ctx.propertyList = propertyList[0:length] } else if replacer.class == classFunction { value := toValue_object(replacer) ctx.replacerFunction = &value } } if spaceValue, exists := call.getArgument(2); exists { if spaceValue.kind == valueObject { switch spaceValue.value.(*_object).class { case classString: spaceValue = toValue_string(spaceValue.string()) case classNumber: spaceValue = spaceValue.numberValue() } } switch spaceValue.kind { case valueString: value := spaceValue.string() if len(value) > 10 { ctx.gap = value[0:10] } else { ctx.gap = value } case valueNumber: value := spaceValue.number().int64 if value > 10 { value = 10 } else if value < 0 { value = 0 } ctx.gap = strings.Repeat(" ", int(value)) } } holder := call.runtime.newObject() holder.put("", call.Argument(0), false) value, exists := builtinJSON_stringifyWalk(ctx, "", holder) if !exists { return Value{} } valueJSON, err := json.Marshal(value) if err != nil { panic(call.runtime.panicTypeError(err.Error())) } if ctx.gap != "" { valueJSON1 := bytes.Buffer{} json.Indent(&valueJSON1, valueJSON, "", ctx.gap) valueJSON = valueJSON1.Bytes() } return toValue_string(string(valueJSON)) } func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, holder *_object) (interface{}, bool) { value := holder.get(key) if value.IsObject() { object := value._object() if toJSON := object.get("toJSON"); toJSON.IsFunction() { value = toJSON.call(ctx.call.runtime, 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 { value = ctx.replacerFunction.call(ctx.call.runtime, toValue_object(holder), key, value) } if value.kind == valueObject { switch value.value.(*_object).class { case classBoolean: value = value._object().value.(Value) case classString: value = toValue_string(value.string()) case classNumber: value = value.numberValue() } } switch value.kind { case valueBoolean: return value.bool(), true case valueString: return value.string(), true case valueNumber: integer := value.number() switch integer.kind { case numberInteger: return integer.int64, true case numberFloat: return integer.float64, true default: return nil, true } case valueNull: return nil, true case valueObject: holder := value._object() if value := value._object(); nil != value { for _, object := range ctx.stack { if holder == object { panic(ctx.call.runtime.panicTypeError("Converting circular structure to JSON")) } } ctx.stack = append(ctx.stack, value) defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }() } if isArray(holder) { var length uint32 switch value := holder.get(propertyLength).value.(type) { case uint32: length = value case int: if value >= 0 { length = uint32(value) } default: panic(ctx.call.runtime.panicTypeError(fmt.Sprintf("JSON.stringify: invalid length: %v (%[1]T)", value))) } array := make([]interface{}, length) for index, _ := range array { name := arrayIndexToString(int64(index)) value, _ := builtinJSON_stringifyWalk(ctx, name, holder) array[index] = value } return array, true } else if holder.class != classFunction { object := map[string]interface{}{} if ctx.propertyList != nil { for _, name := range ctx.propertyList { value, exists := builtinJSON_stringifyWalk(ctx, name, holder) if exists { object[name] = value } } } else { // Go maps are without order, so this doesn't conform to the ECMA ordering // standard, but oh well... holder.enumerate(false, func(name string) bool { value, exists := builtinJSON_stringifyWalk(ctx, name, holder) if exists { object[name] = value } return true }) } return object, true } } return nil, false }