github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/jsoni/extra/binary_as_string_codec.go (about) 1 package extra 2 3 import ( 4 "context" 5 "unicode/utf8" 6 "unsafe" 7 8 "github.com/bingoohuang/gg/pkg/jsoni" 9 "github.com/modern-go/reflect2" 10 ) 11 12 // safeSet holds the value true if the ASCII character with the given array 13 // position can be represented inside a JSON string without any further 14 // escaping. 15 // 16 // All values are true except for the ASCII control characters (0-31), the 17 // double quote ("), and the backslash character ("\"). 18 var safeSet = [utf8.RuneSelf]bool{ 19 ' ': true, 20 '!': true, 21 '"': false, 22 '#': true, 23 '$': true, 24 '%': true, 25 '&': true, 26 '\'': true, 27 '(': true, 28 ')': true, 29 '*': true, 30 '+': true, 31 ',': true, 32 '-': true, 33 '.': true, 34 '/': true, 35 '0': true, 36 '1': true, 37 '2': true, 38 '3': true, 39 '4': true, 40 '5': true, 41 '6': true, 42 '7': true, 43 '8': true, 44 '9': true, 45 ':': true, 46 ';': true, 47 '<': true, 48 '=': true, 49 '>': true, 50 '?': true, 51 '@': true, 52 'A': true, 53 'B': true, 54 'C': true, 55 'D': true, 56 'E': true, 57 'F': true, 58 'G': true, 59 'H': true, 60 'I': true, 61 'J': true, 62 'K': true, 63 'L': true, 64 'M': true, 65 'N': true, 66 'O': true, 67 'P': true, 68 'Q': true, 69 'R': true, 70 'S': true, 71 'T': true, 72 'U': true, 73 'V': true, 74 'W': true, 75 'X': true, 76 'Y': true, 77 'Z': true, 78 '[': true, 79 '\\': false, 80 ']': true, 81 '^': true, 82 '_': true, 83 '`': true, 84 'a': true, 85 'b': true, 86 'c': true, 87 'd': true, 88 'e': true, 89 'f': true, 90 'g': true, 91 'h': true, 92 'i': true, 93 'j': true, 94 'k': true, 95 'l': true, 96 'm': true, 97 'n': true, 98 'o': true, 99 'p': true, 100 'q': true, 101 'r': true, 102 's': true, 103 't': true, 104 'u': true, 105 'v': true, 106 'w': true, 107 'x': true, 108 'y': true, 109 'z': true, 110 '{': true, 111 '|': true, 112 '}': true, 113 '~': true, 114 '\u007f': true, 115 } 116 117 var binaryType = jsoni.PtrElem((*[]byte)(nil)) 118 119 type BinaryAsStringExtension struct { 120 jsoni.DummyExtension 121 } 122 123 func (extension *BinaryAsStringExtension) CreateEncoder(typ reflect2.Type) jsoni.ValEncoder { 124 if typ == binaryType { 125 return &binaryAsStringCodec{} 126 } 127 return nil 128 } 129 130 func (extension *BinaryAsStringExtension) CreateDecoder(typ reflect2.Type) jsoni.ValDecoder { 131 if typ == binaryType { 132 return &binaryAsStringCodec{} 133 } 134 return nil 135 } 136 137 type binaryAsStringCodec struct{} 138 139 func (codec *binaryAsStringCodec) Decode(_ context.Context, ptr unsafe.Pointer, iter *jsoni.Iterator) { 140 rawBytes := iter.ReadStringAsSlice() 141 bytes := make([]byte, 0, len(rawBytes)) 142 for i := 0; i < len(rawBytes); i++ { 143 b := rawBytes[i] 144 if b == '\\' { 145 b2 := rawBytes[i+1] 146 if b2 != '\\' { 147 iter.ReportError("decode binary as string", `\\x is only supported escape`) 148 return 149 } 150 b3 := rawBytes[i+2] 151 if b3 != 'x' { 152 iter.ReportError("decode binary as string", `\\x is only supported escape`) 153 return 154 } 155 b4 := rawBytes[i+3] 156 b5 := rawBytes[i+4] 157 i += 4 158 b = readHex(iter, b4, b5) 159 } 160 bytes = append(bytes, b) 161 } 162 *(*[]byte)(ptr) = bytes 163 } 164 165 func (codec *binaryAsStringCodec) IsEmpty(_ context.Context, ptr unsafe.Pointer, _ bool) bool { 166 return len(*((*[]byte)(ptr))) == 0 167 } 168 169 func (codec *binaryAsStringCodec) Encode(_ context.Context, ptr unsafe.Pointer, stream *jsoni.Stream) { 170 newBuffer := writeBytes(stream.Buffer(), *(*[]byte)(ptr)) 171 stream.SetBuffer(newBuffer) 172 } 173 174 func readHex(iter *jsoni.Iterator, b1, b2 byte) byte { 175 var ret byte 176 if b1 >= '0' && b1 <= '9' { 177 ret = b1 - '0' 178 } else if b1 >= 'a' && b1 <= 'f' { 179 ret = b1 - 'a' + 10 180 } else { 181 iter.ReportError("read hex", "expects 0~9 or a~f, but found "+string([]byte{b1})) 182 return 0 183 } 184 ret *= 16 185 if b2 >= '0' && b2 <= '9' { 186 ret += b2 - '0' 187 } else if b2 >= 'a' && b2 <= 'f' { 188 ret += b2 - 'a' + 10 189 } else { 190 iter.ReportError("read hex", "expects 0~9 or a~f, but found "+string([]byte{b2})) 191 return 0 192 } 193 return ret 194 } 195 196 var hex = "0123456789abcdef" 197 198 func writeBytes(space []byte, s []byte) []byte { 199 space = append(space, '"') 200 // write string, the fast path, without utf8 and escape support 201 var i int 202 var c byte 203 for i, c = range s { 204 if c < utf8.RuneSelf && safeSet[c] { 205 space = append(space, c) 206 } else { 207 break 208 } 209 } 210 if i == len(s)-1 { 211 space = append(space, '"') 212 return space 213 } 214 return writeBytesSlowPath(space, s[i:]) 215 } 216 217 func writeBytesSlowPath(space []byte, s []byte) []byte { 218 start := 0 219 // for the remaining parts, we process them char by char 220 var i int 221 var b byte 222 for i, b = range s { 223 if b >= utf8.RuneSelf { 224 space = append(space, '\\', '\\', 'x', hex[b>>4], hex[b&0xF]) 225 start = i + 1 226 continue 227 } 228 if safeSet[b] { 229 continue 230 } 231 if start < i { 232 space = append(space, s[start:i]...) 233 } 234 space = append(space, '\\', '\\', 'x', hex[b>>4], hex[b&0xF]) 235 start = i + 1 236 } 237 if start < len(s) { 238 space = append(space, s[start:]...) 239 } 240 return append(space, '"') 241 }