json.mx raw

   1  package helpers
   2  
   3  // Minimal JSON serialization for Nostr events.
   4  // No encoding/json dependency.
   5  
   6  // JsonString returns a JSON-escaped string with surrounding quotes.
   7  // Uses string concat to preserve non-ASCII in tinyjs.
   8  func JsonString(s string) string {
   9  	result := "\""
  10  	start := 0
  11  	for i := 0; i < len(s); i++ {
  12  		c := s[i]
  13  		var esc string
  14  		switch c {
  15  		case '"':
  16  			esc = "\\\""
  17  		case '\\':
  18  			esc = "\\\\"
  19  		case '\n':
  20  			esc = "\\n"
  21  		case '\r':
  22  			esc = "\\r"
  23  		case '\t':
  24  			esc = "\\t"
  25  		case '\b':
  26  			esc = "\\b"
  27  		case '\f':
  28  			esc = "\\f"
  29  		default:
  30  			if c < 0x20 {
  31  				esc = "\\u00" + string(hexChars[c>>4]) + string(hexChars[c&0x0f])
  32  			} else {
  33  				continue
  34  			}
  35  		}
  36  		result += s[start:i] + esc
  37  		start = i + 1
  38  	}
  39  	return result + s[start:] + "\""
  40  }
  41  
  42  // JsonGetString extracts a string value for the given key from a JSON object.
  43  // Returns empty string if not found. Handles basic escape sequences.
  44  // Uses string concat (not []byte) to preserve non-ASCII characters in tinyjs.
  45  func JsonGetString(s, key string) string {
  46  	kq := "\"" + key + "\""
  47  	kqLen := len(kq)
  48  	for i := 0; i <= len(s)-kqLen; i++ {
  49  		if s[i:i+kqLen] == kq {
  50  			j := i + kqLen
  51  			for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') {
  52  				j++
  53  			}
  54  			if j >= len(s) || s[j] != ':' {
  55  				continue
  56  			}
  57  			j++
  58  			for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') {
  59  				j++
  60  			}
  61  			if j >= len(s) || s[j] != '"' {
  62  				continue
  63  			}
  64  			j++
  65  			start := j
  66  			result := ""
  67  			for j < len(s) {
  68  				if s[j] == '\\' && j+1 < len(s) {
  69  					result += s[start:j]
  70  					j++
  71  					switch s[j] {
  72  					case '"', '\\', '/':
  73  						result += s[j : j+1]
  74  					case 'n':
  75  						result += "\n"
  76  					case 'r':
  77  						result += "\r"
  78  					case 't':
  79  						result += "\t"
  80  					default:
  81  						result += s[j : j+1]
  82  					}
  83  					j++
  84  					start = j
  85  					continue
  86  				}
  87  				if s[j] == '"' {
  88  					return result + s[start:j]
  89  				}
  90  				j++
  91  			}
  92  		}
  93  	}
  94  	return ""
  95  }
  96  
  97  // JsonGetValue extracts a raw JSON value for the given key from a JSON object.
  98  // Works for any value type: objects, arrays, strings, numbers, bools, null.
  99  // Returns the raw JSON substring. Returns empty string if not found.
 100  func JsonGetValue(s, key string) string {
 101  	kq := "\"" + key + "\""
 102  	kqLen := len(kq)
 103  	for i := 0; i <= len(s)-kqLen; i++ {
 104  		if s[i:i+kqLen] != kq {
 105  			continue
 106  		}
 107  		j := i + kqLen
 108  		for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') {
 109  			j++
 110  		}
 111  		if j >= len(s) || s[j] != ':' {
 112  			continue
 113  		}
 114  		j++
 115  		for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') {
 116  			j++
 117  		}
 118  		if j >= len(s) {
 119  			continue
 120  		}
 121  		start := j
 122  		switch s[j] {
 123  		case '{', '[':
 124  			open := s[j]
 125  			cls := byte('}')
 126  			if open == '[' {
 127  				cls = ']'
 128  			}
 129  			depth := 1
 130  			j++
 131  			for j < len(s) && depth > 0 {
 132  				if s[j] == open {
 133  					depth++
 134  				} else if s[j] == cls {
 135  					depth--
 136  				} else if s[j] == '"' {
 137  					j++
 138  					for j < len(s) && s[j] != '"' {
 139  						if s[j] == '\\' {
 140  							j++
 141  						}
 142  						j++
 143  					}
 144  				}
 145  				j++
 146  			}
 147  			return s[start:j]
 148  		case '"':
 149  			j++
 150  			for j < len(s) && s[j] != '"' {
 151  				if s[j] == '\\' {
 152  					j++
 153  				}
 154  				j++
 155  			}
 156  			return s[start : j+1]
 157  		default:
 158  			for j < len(s) && s[j] != ',' && s[j] != '}' && s[j] != ']' &&
 159  				s[j] != ' ' && s[j] != '\t' && s[j] != '\n' && s[j] != '\r' {
 160  				j++
 161  			}
 162  			return s[start:j]
 163  		}
 164  	}
 165  	return ""
 166  }
 167  
 168  // JsonGetBool returns the boolean value for the given key.
 169  // Returns false if the key is missing or not a bool.
 170  func JsonGetBool(s, key string) bool {
 171  	return JsonGetValue(s, key) == "true"
 172  }
 173  
 174  // JsonGetIntArray returns the integer array for the given key.
 175  // Returns nil if the key is missing or not an array. Non-numeric elements
 176  // are skipped. Handles negative numbers.
 177  func JsonGetIntArray(s, key string) []int {
 178  	raw := JsonGetValue(s, key)
 179  	if len(raw) < 2 || raw[0] != '[' {
 180  		return nil
 181  	}
 182  	var result []int
 183  	i := 1
 184  	for i < len(raw) {
 185  		for i < len(raw) && (raw[i] == ' ' || raw[i] == '\t' || raw[i] == '\n' || raw[i] == '\r' || raw[i] == ',') {
 186  			i++
 187  		}
 188  		if i >= len(raw) || raw[i] == ']' {
 189  			break
 190  		}
 191  		neg := false
 192  		if raw[i] == '-' {
 193  			neg = true
 194  			i++
 195  		}
 196  		digStart := i
 197  		for i < len(raw) && raw[i] >= '0' && raw[i] <= '9' {
 198  			i++
 199  		}
 200  		if i == digStart {
 201  			// Not a number — skip this element.
 202  			for i < len(raw) && raw[i] != ',' && raw[i] != ']' {
 203  				i++
 204  			}
 205  			continue
 206  		}
 207  		n := 0
 208  		for p := digStart; p < i; p++ {
 209  			n = n*10 + int(raw[p]-'0')
 210  		}
 211  		if neg {
 212  			n = -n
 213  		}
 214  		result = append(result, n)
 215  	}
 216  	return result
 217  }
 218  
 219  // Itoa converts int64 to decimal string.
 220  func Itoa(n int64) string {
 221  	if n == 0 {
 222  		return "0"
 223  	}
 224  	neg := false
 225  	if n < 0 {
 226  		neg = true
 227  		n = -n
 228  	}
 229  	var buf [20]byte
 230  	i := len(buf)
 231  	for n > 0 {
 232  		i--
 233  		buf[i] = byte('0' + n%10)
 234  		n /= 10
 235  	}
 236  	if neg {
 237  		i--
 238  		buf[i] = '-'
 239  	}
 240  	return string(buf[i:])
 241  }
 242