aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/robertkrimen/otto/builtin_json.go
blob: aed54bf1264f39604cedb626b93a70c207df7992 (plain) (tree)
1
2
3
4
5
6




                       
             


















                                                              
                                                                       
                       
                                                                 


                                                         
                               


































                                                                                                
                                                                                      




                                                                                               
                                      















































                                                                                          
                                                   











                                                                             
                                                      













                                                                 
                                                   

                                                                  
                                                                                
                                      
                                                                     

                         
                                        
                                 
                                                    





                                                     
                                                          











                                                                   
                              


                                             
                                                               














                                                                                                                    
                                                                         











                                                                                                  
                                                                                                          

         
                                      



                                                             
                                                              
                              
                                                   


                 
                           
                          
                                         
                         
                                           
                         






                                                    

                                        






                                                           
                                                                                                                       





                                                                                   










                                                                                                                                        































                                                                                                          
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 "String":
                    case "Number":
                    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 == "Function" {
            value := toValue_object(replacer)
            ctx.replacerFunction = &value
        }
    }
    if spaceValue, exists := call.getArgument(2); exists {
        if spaceValue.kind == valueObject {
            switch spaceValue.value.(*_object).class {
            case "String":
                spaceValue = toValue_string(spaceValue.string())
            case "Number":
                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 "Boolean":
            value = value._object().value.(Value)
        case "String":
            value = toValue_string(value.string())
        case "Number":
            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("length").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 != "Function" {
            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
}