json.go raw

   1  package nostr
   2  
   3  import "unicode/utf8"
   4  
   5  // AppendJSONString appends s as a JSON string literal to dst using the same
   6  // escaping rules as JavaScript's JSON.stringify:
   7  //
   8  //   - quote and backslash are escaped;
   9  //   - \b \f \n \r \t use their short escapes;
  10  //   - other C0 control bytes become \u00XX with lowercase hex digits;
  11  //   - everything else, including DEL, U+2028 and U+2029, is emitted as UTF-8.
  12  //
  13  // Invalid UTF-8 is replaced with U+FFFD, matching what a JSON parser would
  14  // produce for such input in a JS engine. This is what makes the event ID stable
  15  // across implementations: NIP-01 defines the serialization in terms of
  16  // JSON.stringify, not in terms of Go's encoder.
  17  func AppendJSONString(dst []byte, s string) []byte {
  18  	dst = append(dst, '"')
  19  	start := 0
  20  	for i := 0; i < len(s); {
  21  		c := s[i]
  22  		if c < utf8.RuneSelf {
  23  			if c >= 0x20 && c != '"' && c != '\\' {
  24  				i++
  25  				continue
  26  			}
  27  			dst = append(dst, s[start:i]...)
  28  			switch c {
  29  			case '"':
  30  				dst = append(dst, '\\', '"')
  31  			case '\\':
  32  				dst = append(dst, '\\', '\\')
  33  			case '\b':
  34  				dst = append(dst, '\\', 'b')
  35  			case '\f':
  36  				dst = append(dst, '\\', 'f')
  37  			case '\n':
  38  				dst = append(dst, '\\', 'n')
  39  			case '\r':
  40  				dst = append(dst, '\\', 'r')
  41  			case '\t':
  42  				dst = append(dst, '\\', 't')
  43  			default:
  44  				const hexDigits = "0123456789abcdef"
  45  				dst = append(dst, '\\', 'u', '0', '0', hexDigits[c>>4], hexDigits[c&0x0f])
  46  			}
  47  			i++
  48  			start = i
  49  			continue
  50  		}
  51  		r, size := utf8.DecodeRuneInString(s[i:])
  52  		if r == utf8.RuneError && size == 1 {
  53  			dst = append(dst, s[start:i]...)
  54  			dst = append(dst, "\uFFFD"...)
  55  			i++
  56  			start = i
  57  			continue
  58  		}
  59  		i += size
  60  	}
  61  	dst = append(dst, s[start:]...)
  62  	return append(dst, '"')
  63  }
  64