@@ -3,7 +3,7 @@ package main
33import (
44 "encoding/json"
55 "fmt"
6- "regexp "
6+ "unicode "
77)
88
99// The javascript reserved words cannot be used as unquoted keys
@@ -159,20 +159,48 @@ func formatValue(s interface{}) string {
159159// a key with spaces -> true
160160// 1startsWithANumber -> true
161161func keyMustBeQuoted (s string ) bool {
162- r := regexp .MustCompile (`^[a-zA-Z_][a-zA-Z0-9_]*$` )
163- if ! r .MatchString (s ) {
164- return true
165- }
166-
167- // Check the list of reserved words
162+ // Check the list of reserved words first
163+ // to avoid more expensive checks where possible
168164 for _ , i := range reservedWords {
169165 if s == i {
170166 return true
171167 }
172168 }
169+
170+ for i , r := range s {
171+ if i == 0 && ! validFirstRune (r ) {
172+ return true
173+ }
174+ if ! validSecondaryRune (r ) {
175+ return true
176+ }
177+ }
178+
173179 return false
174180}
175181
182+ // validFirstRune returns true for runes that are valid
183+ // as the first rune in a key.
184+ // E.g:
185+ // 'r' -> true
186+ // '7' -> false
187+ func validFirstRune (r rune ) bool {
188+ return unicode .In (r ,
189+ unicode .Lu ,
190+ unicode .Ll ,
191+ unicode .Lm ,
192+ unicode .Lo ,
193+ unicode .Nl ,
194+ ) || r == '$' || r == '_'
195+ }
196+
197+ // validSecondaryRune returns true for runes that are valid
198+ // as anything other than the first rune in a key.
199+ func validSecondaryRune (r rune ) bool {
200+ return validFirstRune (r ) ||
201+ unicode .In (r , unicode .Mn , unicode .Mc , unicode .Nd , unicode .Pc )
202+ }
203+
176204// makePrefix takes the previous prefix and the next key and
177205// returns a new prefix or an error on failure
178206func makePrefix (prev string , next interface {}) (string , error ) {
@@ -181,9 +209,11 @@ func makePrefix(prev string, next interface{}) (string, error) {
181209 return fmt .Sprintf ("%s[%d]" , prev , v ), nil
182210 case string :
183211 if keyMustBeQuoted (v ) {
184- return fmt .Sprintf ("%s[%s]" , prev , formatValue (v )), nil
212+ // This is a fairly hot code path, and concatination has
213+ // proven to be faster than fmt.Sprintf, despite the allocations
214+ return prev + "[" + formatValue (v ) + "]" , nil
185215 }
186- return fmt . Sprintf ( "%s.%s" , prev , v ) , nil
216+ return prev + "." + v , nil
187217 default :
188218 return "" , fmt .Errorf ("could not form prefix for %#v" , next )
189219 }
0 commit comments