package helpers // Minimal JSON serialization for Nostr events. // No encoding/json dependency. // JsonString returns a JSON-escaped string with surrounding quotes. // Uses string concat to preserve non-ASCII in tinyjs. func JsonString(s string) string { result := "\"" start := 0 for i := 0; i < len(s); i++ { c := s[i] var esc string switch c { case '"': esc = "\\\"" case '\\': esc = "\\\\" case '\n': esc = "\\n" case '\r': esc = "\\r" case '\t': esc = "\\t" case '\b': esc = "\\b" case '\f': esc = "\\f" default: if c < 0x20 { esc = "\\u00" + string(hexChars[c>>4]) + string(hexChars[c&0x0f]) } else { continue } } result += s[start:i] + esc start = i + 1 } return result + s[start:] + "\"" } // JsonGetString extracts a string value for the given key from a JSON object. // Returns empty string if not found. Handles basic escape sequences. // Uses string concat (not []byte) to preserve non-ASCII characters in tinyjs. func JsonGetString(s, key string) string { kq := "\"" + key + "\"" kqLen := len(kq) for i := 0; i <= len(s)-kqLen; i++ { if s[i:i+kqLen] == kq { j := i + kqLen for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') { j++ } if j >= len(s) || s[j] != ':' { continue } j++ for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') { j++ } if j >= len(s) || s[j] != '"' { continue } j++ start := j result := "" for j < len(s) { if s[j] == '\\' && j+1 < len(s) { result += s[start:j] j++ switch s[j] { case '"', '\\', '/': result += s[j : j+1] case 'n': result += "\n" case 'r': result += "\r" case 't': result += "\t" default: result += s[j : j+1] } j++ start = j continue } if s[j] == '"' { return result + s[start:j] } j++ } } } return "" } // JsonGetValue extracts a raw JSON value for the given key from a JSON object. // Works for any value type: objects, arrays, strings, numbers, bools, null. // Returns the raw JSON substring. Returns empty string if not found. func JsonGetValue(s, key string) string { kq := "\"" + key + "\"" kqLen := len(kq) for i := 0; i <= len(s)-kqLen; i++ { if s[i:i+kqLen] != kq { continue } j := i + kqLen for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') { j++ } if j >= len(s) || s[j] != ':' { continue } j++ for j < len(s) && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r') { j++ } if j >= len(s) { continue } start := j switch s[j] { case '{', '[': open := s[j] cls := byte('}') if open == '[' { cls = ']' } depth := 1 j++ for j < len(s) && depth > 0 { if s[j] == open { depth++ } else if s[j] == cls { depth-- } else if s[j] == '"' { j++ for j < len(s) && s[j] != '"' { if s[j] == '\\' { j++ } j++ } } j++ } return s[start:j] case '"': j++ for j < len(s) && s[j] != '"' { if s[j] == '\\' { j++ } j++ } return s[start : j+1] default: for j < len(s) && s[j] != ',' && s[j] != '}' && s[j] != ']' && s[j] != ' ' && s[j] != '\t' && s[j] != '\n' && s[j] != '\r' { j++ } return s[start:j] } } return "" } // JsonGetBool returns the boolean value for the given key. // Returns false if the key is missing or not a bool. func JsonGetBool(s, key string) bool { return JsonGetValue(s, key) == "true" } // JsonGetIntArray returns the integer array for the given key. // Returns nil if the key is missing or not an array. Non-numeric elements // are skipped. Handles negative numbers. func JsonGetIntArray(s, key string) []int { raw := JsonGetValue(s, key) if len(raw) < 2 || raw[0] != '[' { return nil } var result []int i := 1 for i < len(raw) { for i < len(raw) && (raw[i] == ' ' || raw[i] == '\t' || raw[i] == '\n' || raw[i] == '\r' || raw[i] == ',') { i++ } if i >= len(raw) || raw[i] == ']' { break } neg := false if raw[i] == '-' { neg = true i++ } digStart := i for i < len(raw) && raw[i] >= '0' && raw[i] <= '9' { i++ } if i == digStart { // Not a number — skip this element. for i < len(raw) && raw[i] != ',' && raw[i] != ']' { i++ } continue } n := 0 for p := digStart; p < i; p++ { n = n*10 + int(raw[p]-'0') } if neg { n = -n } result = append(result, n) } return result } // Itoa converts int64 to decimal string. func Itoa(n int64) string { if n == 0 { return "0" } neg := false if n < 0 { neg = true n = -n } var buf [20]byte i := len(buf) for n > 0 { i-- buf[i] = byte('0' + n%10) n /= 10 } if neg { i-- buf[i] = '-' } return string(buf[i:]) }