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 }