package goja
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
"github.com/dop251/goja/unistring"
)
const hex = "0123456789abcdef"
func (r *Runtime ) builtinJSON_parse (call FunctionCall ) Value {
d := json .NewDecoder (strings .NewReader (call .Argument (0 ).toString ().String ()))
value , err := r .builtinJSON_decodeValue (d )
if errors .Is (err , io .EOF ) {
panic (r .newError (r .getSyntaxError (), "Unexpected end of JSON input (%v)" , err .Error()))
}
if err != nil {
panic (r .newError (r .getSyntaxError (), err .Error()))
}
if tok , err := d .Token (); err != io .EOF {
panic (r .newError (r .getSyntaxError (), "Unexpected token at the end: %v" , tok ))
}
var reviver func (FunctionCall ) Value
if arg1 := call .Argument (1 ); arg1 != _undefined {
reviver , _ = arg1 .ToObject (r ).self .assertCallable ()
}
if reviver != nil {
root := r .NewObject ()
createDataPropertyOrThrow (root , stringEmpty , value )
return r .builtinJSON_reviveWalk (reviver , root , stringEmpty )
}
return value
}
func (r *Runtime ) builtinJSON_decodeToken (d *json .Decoder , tok json .Token ) (Value , error ) {
switch tok := tok .(type ) {
case json .Delim :
switch tok {
case '{' :
return r .builtinJSON_decodeObject (d )
case '[' :
return r .builtinJSON_decodeArray (d )
}
case nil :
return _null , nil
case string :
return newStringValue (tok ), nil
case float64 :
return floatToValue (tok ), nil
case bool :
if tok {
return valueTrue , nil
}
return valueFalse , nil
}
return nil , fmt .Errorf ("Unexpected token (%T): %v" , tok , tok )
}
func (r *Runtime ) builtinJSON_decodeValue (d *json .Decoder ) (Value , error ) {
tok , err := d .Token ()
if err != nil {
return nil , err
}
return r .builtinJSON_decodeToken (d , tok )
}
func (r *Runtime ) builtinJSON_decodeObject (d *json .Decoder ) (*Object , error ) {
object := r .NewObject ()
for {
key , end , err := r .builtinJSON_decodeObjectKey (d )
if err != nil {
return nil , err
}
if end {
break
}
value , err := r .builtinJSON_decodeValue (d )
if err != nil {
return nil , err
}
object .self ._putProp (unistring .NewFromString (key ), value , true , true , true )
}
return object , nil
}
func (r *Runtime ) builtinJSON_decodeObjectKey (d *json .Decoder ) (string , bool , error ) {
tok , err := d .Token ()
if err != nil {
return "" , false , err
}
switch tok := tok .(type ) {
case json .Delim :
if tok == '}' {
return "" , true , nil
}
case string :
return tok , false , nil
}
return "" , false , fmt .Errorf ("Unexpected token (%T): %v" , tok , tok )
}
func (r *Runtime ) builtinJSON_decodeArray (d *json .Decoder ) (*Object , error ) {
var arrayValue []Value
for {
tok , err := d .Token ()
if err != nil {
return nil , err
}
if delim , ok := tok .(json .Delim ); ok {
if delim == ']' {
break
}
}
value , err := r .builtinJSON_decodeToken (d , tok )
if err != nil {
return nil , err
}
arrayValue = append (arrayValue , value )
}
return r .newArrayValues (arrayValue ), nil
}
func (r *Runtime ) builtinJSON_reviveWalk (reviver func (FunctionCall ) Value , holder *Object , name Value ) Value {
value := nilSafe (holder .get (name , nil ))
if object , ok := value .(*Object ); ok {
if isArray (object ) {
length := toLength (object .self .getStr ("length" , nil ))
for index := int64 (0 ); index < length ; index ++ {
name := asciiString (strconv .FormatInt (index , 10 ))
value := r .builtinJSON_reviveWalk (reviver , object , name )
if value == _undefined {
object .delete (name , false )
} else {
createDataProperty (object , name , value )
}
}
} else {
for _ , name := range object .self .stringKeys (false , nil ) {
value := r .builtinJSON_reviveWalk (reviver , object , name )
if value == _undefined {
object .self .deleteStr (name .string (), false )
} else {
createDataProperty (object , name , value )
}
}
}
}
return reviver (FunctionCall {
This : holder ,
Arguments : []Value {name , value },
})
}
type _builtinJSON_stringifyContext struct {
r *Runtime
stack []*Object
propertyList []Value
replacerFunction func (FunctionCall ) Value
gap, indent string
buf bytes .Buffer
allAscii bool
}
func (r *Runtime ) builtinJSON_stringify (call FunctionCall ) Value {
ctx := _builtinJSON_stringifyContext {
r : r ,
allAscii : true ,
}
replacer , _ := call .Argument (1 ).(*Object )
if replacer != nil {
if isArray (replacer ) {
length := toLength (replacer .self .getStr ("length" , nil ))
seen := map [string ]bool {}
propertyList := make ([]Value , length )
length = 0
for index := range propertyList {
var name string
value := replacer .self .getIdx (valueInt (int64 (index )), nil )
switch v := value .(type ) {
case valueFloat , valueInt , String :
name = value .String ()
case *Object :
switch v .self .className () {
case classNumber , classString :
name = value .String ()
default :
continue
}
default :
continue
}
if seen [name ] {
continue
}
seen [name ] = true
propertyList [length ] = newStringValue (name )
length += 1
}
ctx .propertyList = propertyList [0 :length ]
} else if c , ok := replacer .self .assertCallable (); ok {
ctx .replacerFunction = c
}
}
if spaceValue := call .Argument (2 ); spaceValue != _undefined {
if o , ok := spaceValue .(*Object ); ok {
switch oImpl := o .self .(type ) {
case *primitiveValueObject :
switch oImpl .pValue .(type ) {
case valueInt , valueFloat :
spaceValue = o .ToNumber ()
}
case *stringObject :
spaceValue = o .ToString ()
}
}
isNum := false
var num int64
if i , ok := spaceValue .(valueInt ); ok {
num = int64 (i )
isNum = true
} else if f , ok := spaceValue .(valueFloat ); ok {
num = int64 (f )
isNum = true
}
if isNum {
if num > 0 {
if num > 10 {
num = 10
}
ctx .gap = strings .Repeat (" " , int (num ))
}
} else {
if s , ok := spaceValue .(String ); ok {
str := s .String ()
if len (str ) > 10 {
ctx .gap = str [:10 ]
} else {
ctx .gap = str
}
}
}
}
if ctx .do (call .Argument (0 )) {
if ctx .allAscii {
return asciiString (ctx .buf .String ())
} else {
return &importedString {
s : ctx .buf .String (),
}
}
}
return _undefined
}
func (ctx *_builtinJSON_stringifyContext ) do (v Value ) bool {
holder := ctx .r .NewObject ()
createDataPropertyOrThrow (holder , stringEmpty , v )
return ctx .str (stringEmpty , holder )
}
func (ctx *_builtinJSON_stringifyContext ) str (key Value , holder *Object ) bool {
value := nilSafe (holder .get (key , nil ))
switch value .(type ) {
case *Object , *valueBigInt :
if toJSON , ok := ctx .r .getVStr (value , "toJSON" ).(*Object ); ok {
if c , ok := toJSON .self .assertCallable (); ok {
value = c (FunctionCall {
This : value ,
Arguments : []Value {key },
})
}
}
}
if ctx .replacerFunction != nil {
value = ctx .replacerFunction (FunctionCall {
This : holder ,
Arguments : []Value {key , value },
})
}
if o , ok := value .(*Object ); ok {
switch o1 := o .self .(type ) {
case *primitiveValueObject :
switch pValue := o1 .pValue .(type ) {
case valueInt , valueFloat :
value = o .ToNumber ()
default :
value = pValue
}
case *stringObject :
value = o .toString ()
case *objectGoReflect :
if o1 .toJson != nil {
value = ctx .r .ToValue (o1 .toJson ())
} else if v , ok := o1 .origValue .Interface ().(json .Marshaler ); ok {
b , err := v .MarshalJSON ()
if err != nil {
panic (ctx .r .NewGoError (err ))
}
ctx .buf .Write (b )
ctx .allAscii = false
return true
} else {
switch o1 .className () {
case classNumber :
value = o1 .val .ordinaryToPrimitiveNumber ()
case classString :
value = o1 .val .ordinaryToPrimitiveString ()
case classBoolean :
if o .ToInteger () != 0 {
value = valueTrue
} else {
value = valueFalse
}
}
if o1 .exportType () == typeBigInt {
value = o1 .val .ordinaryToPrimitiveNumber ()
}
}
}
}
switch value1 := value .(type ) {
case valueBool :
if value1 {
ctx .buf .WriteString ("true" )
} else {
ctx .buf .WriteString ("false" )
}
case String :
ctx .quote (value1 )
case valueInt :
ctx .buf .WriteString (value .String ())
case valueFloat :
if !math .IsNaN (float64 (value1 )) && !math .IsInf (float64 (value1 ), 0 ) {
ctx .buf .WriteString (value .String ())
} else {
ctx .buf .WriteString ("null" )
}
case valueNull :
ctx .buf .WriteString ("null" )
case *valueBigInt :
ctx .r .typeErrorResult (true , "Do not know how to serialize a BigInt" )
case *Object :
for _ , object := range ctx .stack {
if value1 .SameAs (object ) {
ctx .r .typeErrorResult (true , "Converting circular structure to JSON" )
}
}
ctx .stack = append (ctx .stack , value1 )
defer func () { ctx .stack = ctx .stack [:len (ctx .stack )-1 ] }()
if _ , ok := value1 .self .assertCallable (); !ok {
if isArray (value1 ) {
ctx .ja (value1 )
} else {
ctx .jo (value1 )
}
} else {
return false
}
default :
return false
}
return true
}
func (ctx *_builtinJSON_stringifyContext ) ja (array *Object ) {
var stepback string
if ctx .gap != "" {
stepback = ctx .indent
ctx .indent += ctx .gap
}
length := toLength (array .self .getStr ("length" , nil ))
if length == 0 {
ctx .buf .WriteString ("[]" )
return
}
ctx .buf .WriteByte ('[' )
var separator string
if ctx .gap != "" {
ctx .buf .WriteByte ('\n' )
ctx .buf .WriteString (ctx .indent )
separator = ",\n" + ctx .indent
} else {
separator = ","
}
for i := int64 (0 ); i < length ; i ++ {
if !ctx .str (asciiString (strconv .FormatInt (i , 10 )), array ) {
ctx .buf .WriteString ("null" )
}
if i < length -1 {
ctx .buf .WriteString (separator )
}
}
if ctx .gap != "" {
ctx .buf .WriteByte ('\n' )
ctx .buf .WriteString (stepback )
ctx .indent = stepback
}
ctx .buf .WriteByte (']' )
}
func (ctx *_builtinJSON_stringifyContext ) jo (object *Object ) {
var stepback string
if ctx .gap != "" {
stepback = ctx .indent
ctx .indent += ctx .gap
}
ctx .buf .WriteByte ('{' )
mark := ctx .buf .Len ()
var separator string
if ctx .gap != "" {
ctx .buf .WriteByte ('\n' )
ctx .buf .WriteString (ctx .indent )
separator = ",\n" + ctx .indent
} else {
separator = ","
}
var props []Value
if ctx .propertyList == nil {
props = object .self .stringKeys (false , nil )
} else {
props = ctx .propertyList
}
empty := true
for _ , name := range props {
off := ctx .buf .Len ()
if !empty {
ctx .buf .WriteString (separator )
}
ctx .quote (name .toString ())
if ctx .gap != "" {
ctx .buf .WriteString (": " )
} else {
ctx .buf .WriteByte (':' )
}
if ctx .str (name , object ) {
if empty {
empty = false
}
} else {
ctx .buf .Truncate (off )
}
}
if empty {
ctx .buf .Truncate (mark )
} else {
if ctx .gap != "" {
ctx .buf .WriteByte ('\n' )
ctx .buf .WriteString (stepback )
ctx .indent = stepback
}
}
ctx .buf .WriteByte ('}' )
}
func (ctx *_builtinJSON_stringifyContext ) quote (str String ) {
ctx .buf .WriteByte ('"' )
reader := &lenientUtf16Decoder {utf16Reader : str .utf16Reader ()}
for {
r , _ , err := reader .ReadRune ()
if err != nil {
break
}
switch r {
case '"' , '\\' :
ctx .buf .WriteByte ('\\' )
ctx .buf .WriteByte (byte (r ))
case 0x08 :
ctx .buf .WriteString (`\b` )
case 0x09 :
ctx .buf .WriteString (`\t` )
case 0x0A :
ctx .buf .WriteString (`\n` )
case 0x0C :
ctx .buf .WriteString (`\f` )
case 0x0D :
ctx .buf .WriteString (`\r` )
default :
if r < 0x20 {
ctx .buf .WriteString (`\u00` )
ctx .buf .WriteByte (hex [r >>4 ])
ctx .buf .WriteByte (hex [r &0xF ])
} else {
if utf16 .IsSurrogate (r ) {
ctx .buf .WriteString (`\u` )
ctx .buf .WriteByte (hex [r >>12 ])
ctx .buf .WriteByte (hex [(r >>8 )&0xF ])
ctx .buf .WriteByte (hex [(r >>4 )&0xF ])
ctx .buf .WriteByte (hex [r &0xF ])
} else {
ctx .buf .WriteRune (r )
if ctx .allAscii && r >= utf8 .RuneSelf {
ctx .allAscii = false
}
}
}
}
}
ctx .buf .WriteByte ('"' )
}
func (r *Runtime ) getJSON () *Object {
ret := r .global .JSON
if ret == nil {
JSON := r .newBaseObject (r .global .ObjectPrototype , classObject )
ret = JSON .val
r .global .JSON = ret
JSON ._putProp ("parse" , r .newNativeFunc (r .builtinJSON_parse , "parse" , 2 ), true , false , true )
JSON ._putProp ("stringify" , r .newNativeFunc (r .builtinJSON_stringify , "stringify" , 3 ), true , false , true )
JSON ._putSym (SymToStringTag , valueProp (asciiString (classJSON ), false , false , true ))
}
return ret
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .