github.com/robertkrimen/otto@v0.2.1/builtin_json.go (about)

     1  package otto
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  )
     9  
    10  type _builtinJSON_parseContext struct {
    11  	call    FunctionCall
    12  	reviver Value
    13  }
    14  
    15  func builtinJSON_parse(call FunctionCall) Value {
    16  	ctx := _builtinJSON_parseContext{
    17  		call: call,
    18  	}
    19  	revive := false
    20  	if reviver := call.Argument(1); reviver.isCallable() {
    21  		revive = true
    22  		ctx.reviver = reviver
    23  	}
    24  
    25  	var root interface{}
    26  	err := json.Unmarshal([]byte(call.Argument(0).string()), &root)
    27  	if err != nil {
    28  		panic(call.runtime.panicSyntaxError(err.Error()))
    29  	}
    30  	value, exists := builtinJSON_parseWalk(ctx, root)
    31  	if !exists {
    32  		value = Value{}
    33  	}
    34  	if revive {
    35  		root := ctx.call.runtime.newObject()
    36  		root.put("", value, false)
    37  		return builtinJSON_reviveWalk(ctx, root, "")
    38  	}
    39  	return value
    40  }
    41  
    42  func builtinJSON_reviveWalk(ctx _builtinJSON_parseContext, holder *_object, name string) Value {
    43  	value := holder.get(name)
    44  	if object := value._object(); object != nil {
    45  		if isArray(object) {
    46  			length := int64(objectLength(object))
    47  			for index := int64(0); index < length; index += 1 {
    48  				name := arrayIndexToString(index)
    49  				value := builtinJSON_reviveWalk(ctx, object, name)
    50  				if value.IsUndefined() {
    51  					object.delete(name, false)
    52  				} else {
    53  					object.defineProperty(name, value, 0111, false)
    54  				}
    55  			}
    56  		} else {
    57  			object.enumerate(false, func(name string) bool {
    58  				value := builtinJSON_reviveWalk(ctx, object, name)
    59  				if value.IsUndefined() {
    60  					object.delete(name, false)
    61  				} else {
    62  					object.defineProperty(name, value, 0111, false)
    63  				}
    64  				return true
    65  			})
    66  		}
    67  	}
    68  	return ctx.reviver.call(ctx.call.runtime, toValue_object(holder), name, value)
    69  }
    70  
    71  func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) {
    72  	switch value := rawValue.(type) {
    73  	case nil:
    74  		return nullValue, true
    75  	case bool:
    76  		return toValue_bool(value), true
    77  	case string:
    78  		return toValue_string(value), true
    79  	case float64:
    80  		return toValue_float64(value), true
    81  	case []interface{}:
    82  		arrayValue := make([]Value, len(value))
    83  		for index, rawValue := range value {
    84  			if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
    85  				arrayValue[index] = value
    86  			}
    87  		}
    88  		return toValue_object(ctx.call.runtime.newArrayOf(arrayValue)), true
    89  	case map[string]interface{}:
    90  		object := ctx.call.runtime.newObject()
    91  		for name, rawValue := range value {
    92  			if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
    93  				object.put(name, value, false)
    94  			}
    95  		}
    96  		return toValue_object(object), true
    97  	}
    98  	return Value{}, false
    99  }
   100  
   101  type _builtinJSON_stringifyContext struct {
   102  	call             FunctionCall
   103  	stack            []*_object
   104  	propertyList     []string
   105  	replacerFunction *Value
   106  	gap              string
   107  }
   108  
   109  func builtinJSON_stringify(call FunctionCall) Value {
   110  	ctx := _builtinJSON_stringifyContext{
   111  		call:  call,
   112  		stack: []*_object{nil},
   113  	}
   114  	replacer := call.Argument(1)._object()
   115  	if replacer != nil {
   116  		if isArray(replacer) {
   117  			length := objectLength(replacer)
   118  			seen := map[string]bool{}
   119  			propertyList := make([]string, length)
   120  			length = 0
   121  			for index, _ := range propertyList {
   122  				value := replacer.get(arrayIndexToString(int64(index)))
   123  				switch value.kind {
   124  				case valueObject:
   125  					switch value.value.(*_object).class {
   126  					case classString:
   127  					case classNumber:
   128  					default:
   129  						continue
   130  					}
   131  				case valueString:
   132  				case valueNumber:
   133  				default:
   134  					continue
   135  				}
   136  				name := value.string()
   137  				if seen[name] {
   138  					continue
   139  				}
   140  				seen[name] = true
   141  				length += 1
   142  				propertyList[index] = name
   143  			}
   144  			ctx.propertyList = propertyList[0:length]
   145  		} else if replacer.class == classFunction {
   146  			value := toValue_object(replacer)
   147  			ctx.replacerFunction = &value
   148  		}
   149  	}
   150  	if spaceValue, exists := call.getArgument(2); exists {
   151  		if spaceValue.kind == valueObject {
   152  			switch spaceValue.value.(*_object).class {
   153  			case classString:
   154  				spaceValue = toValue_string(spaceValue.string())
   155  			case classNumber:
   156  				spaceValue = spaceValue.numberValue()
   157  			}
   158  		}
   159  		switch spaceValue.kind {
   160  		case valueString:
   161  			value := spaceValue.string()
   162  			if len(value) > 10 {
   163  				ctx.gap = value[0:10]
   164  			} else {
   165  				ctx.gap = value
   166  			}
   167  		case valueNumber:
   168  			value := spaceValue.number().int64
   169  			if value > 10 {
   170  				value = 10
   171  			} else if value < 0 {
   172  				value = 0
   173  			}
   174  			ctx.gap = strings.Repeat(" ", int(value))
   175  		}
   176  	}
   177  	holder := call.runtime.newObject()
   178  	holder.put("", call.Argument(0), false)
   179  	value, exists := builtinJSON_stringifyWalk(ctx, "", holder)
   180  	if !exists {
   181  		return Value{}
   182  	}
   183  	valueJSON, err := json.Marshal(value)
   184  	if err != nil {
   185  		panic(call.runtime.panicTypeError(err.Error()))
   186  	}
   187  	if ctx.gap != "" {
   188  		valueJSON1 := bytes.Buffer{}
   189  		json.Indent(&valueJSON1, valueJSON, "", ctx.gap)
   190  		valueJSON = valueJSON1.Bytes()
   191  	}
   192  	return toValue_string(string(valueJSON))
   193  }
   194  
   195  func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, holder *_object) (interface{}, bool) {
   196  	value := holder.get(key)
   197  
   198  	if value.IsObject() {
   199  		object := value._object()
   200  		if toJSON := object.get("toJSON"); toJSON.IsFunction() {
   201  			value = toJSON.call(ctx.call.runtime, value, key)
   202  		} else {
   203  			// If the object is a GoStruct or something that implements json.Marshaler
   204  			if object.objectClass.marshalJSON != nil {
   205  				marshaler := object.objectClass.marshalJSON(object)
   206  				if marshaler != nil {
   207  					return marshaler, true
   208  				}
   209  			}
   210  		}
   211  	}
   212  
   213  	if ctx.replacerFunction != nil {
   214  		value = ctx.replacerFunction.call(ctx.call.runtime, toValue_object(holder), key, value)
   215  	}
   216  
   217  	if value.kind == valueObject {
   218  		switch value.value.(*_object).class {
   219  		case classBoolean:
   220  			value = value._object().value.(Value)
   221  		case classString:
   222  			value = toValue_string(value.string())
   223  		case classNumber:
   224  			value = value.numberValue()
   225  		}
   226  	}
   227  
   228  	switch value.kind {
   229  	case valueBoolean:
   230  		return value.bool(), true
   231  	case valueString:
   232  		return value.string(), true
   233  	case valueNumber:
   234  		integer := value.number()
   235  		switch integer.kind {
   236  		case numberInteger:
   237  			return integer.int64, true
   238  		case numberFloat:
   239  			return integer.float64, true
   240  		default:
   241  			return nil, true
   242  		}
   243  	case valueNull:
   244  		return nil, true
   245  	case valueObject:
   246  		holder := value._object()
   247  		if value := value._object(); nil != value {
   248  			for _, object := range ctx.stack {
   249  				if holder == object {
   250  					panic(ctx.call.runtime.panicTypeError("Converting circular structure to JSON"))
   251  				}
   252  			}
   253  			ctx.stack = append(ctx.stack, value)
   254  			defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
   255  		}
   256  		if isArray(holder) {
   257  			var length uint32
   258  			switch value := holder.get(propertyLength).value.(type) {
   259  			case uint32:
   260  				length = value
   261  			case int:
   262  				if value >= 0 {
   263  					length = uint32(value)
   264  				}
   265  			default:
   266  				panic(ctx.call.runtime.panicTypeError(fmt.Sprintf("JSON.stringify: invalid length: %v (%[1]T)", value)))
   267  			}
   268  			array := make([]interface{}, length)
   269  			for index, _ := range array {
   270  				name := arrayIndexToString(int64(index))
   271  				value, _ := builtinJSON_stringifyWalk(ctx, name, holder)
   272  				array[index] = value
   273  			}
   274  			return array, true
   275  		} else if holder.class != classFunction {
   276  			object := map[string]interface{}{}
   277  			if ctx.propertyList != nil {
   278  				for _, name := range ctx.propertyList {
   279  					value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
   280  					if exists {
   281  						object[name] = value
   282  					}
   283  				}
   284  			} else {
   285  				// Go maps are without order, so this doesn't conform to the ECMA ordering
   286  				// standard, but oh well...
   287  				holder.enumerate(false, func(name string) bool {
   288  					value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
   289  					if exists {
   290  						object[name] = value
   291  					}
   292  					return true
   293  				})
   294  			}
   295  			return object, true
   296  		}
   297  	}
   298  	return nil, false
   299  }