github.com/3JoB/go-json@v0.10.4/encode.go (about) 1 package json 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "unsafe" 8 9 "github.com/3JoB/go-json/internal/encoder" 10 "github.com/3JoB/go-json/internal/encoder/vm" 11 "github.com/3JoB/go-json/internal/encoder/vm_color" 12 "github.com/3JoB/go-json/internal/encoder/vm_color_indent" 13 "github.com/3JoB/go-json/internal/encoder/vm_indent" 14 "github.com/3JoB/unsafeConvert" 15 ) 16 17 // An Encoder writes JSON values to an output stream. 18 type Encoder struct { 19 w io.Writer 20 enabledIndent bool 21 enabledHTMLEscape bool 22 prefix string 23 indentStr string 24 } 25 26 // NewEncoder returns a new encoder that writes to w. 27 func NewEncoder(w io.Writer) *Encoder { 28 return &Encoder{w: w, enabledHTMLEscape: true} 29 } 30 31 // Encode writes the JSON encoding of v to the stream, followed by a newline character. 32 // 33 // See the documentation for Marshal for details about the conversion of Go values to JSON. 34 func (e *Encoder) Encode(v any) error { 35 return e.EncodeWithOption(v) 36 } 37 38 // EncodeWithOption call Encode with EncodeOption. 39 func (e *Encoder) EncodeWithOption(v any, optFuncs ...EncodeOptionFunc) error { 40 ctx := encoder.TakeRuntimeContext() 41 ctx.Option.Flag = 0 42 43 err := e.encodeWithOption(ctx, v, optFuncs...) 44 45 encoder.ReleaseRuntimeContext(ctx) 46 return err 47 } 48 49 // EncodeContext call Encode with context.Context and EncodeOption. 50 func (e *Encoder) EncodeContext(ctx context.Context, v any, optFuncs ...EncodeOptionFunc) error { 51 rctx := encoder.TakeRuntimeContext() 52 rctx.Option.Flag = 0 53 rctx.Option.Flag |= encoder.ContextOption 54 rctx.Option.Context = ctx 55 56 err := e.encodeWithOption(rctx, v, optFuncs...) 57 58 encoder.ReleaseRuntimeContext(rctx) 59 return err 60 } 61 62 func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v any, optFuncs ...EncodeOptionFunc) error { 63 if e.enabledHTMLEscape { 64 ctx.Option.Flag |= encoder.HTMLEscapeOption 65 } 66 ctx.Option.Flag |= encoder.NormalizeUTF8Option 67 ctx.Option.DebugOut = os.Stdout 68 for _, optFunc := range optFuncs { 69 optFunc(ctx.Option) 70 } 71 var ( 72 buf []byte 73 err error 74 ) 75 if e.enabledIndent { 76 buf, err = encodeIndent(ctx, v, e.prefix, e.indentStr) 77 } else { 78 buf, err = encode(ctx, v) 79 } 80 if err != nil { 81 return err 82 } 83 if e.enabledIndent { 84 buf = buf[:len(buf)-2] 85 } else { 86 buf = buf[:len(buf)-1] 87 } 88 buf = append(buf, '\n') 89 if _, err := e.w.Write(buf); err != nil { 90 return err 91 } 92 return nil 93 } 94 95 // SetEscapeHTML specifies whether problematic HTML characters should be escaped inside JSON quoted strings. 96 // The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e to avoid certain safety problems that can arise when embedding JSON in HTML. 97 // 98 // In non-HTML settings where the escaping interferes with the readability of the output, SetEscapeHTML(false) disables this behavior. 99 func (e *Encoder) SetEscapeHTML(on bool) { 100 e.enabledHTMLEscape = on 101 } 102 103 // SetIndent instructs the encoder to format each subsequent encoded value as if indented by the package-level function Indent(dst, src, prefix, indent). 104 // Calling SetIndent("", "") disables indentation. 105 func (e *Encoder) SetIndent(prefix, indent string) { 106 if prefix == "" && indent == "" { 107 e.enabledIndent = false 108 return 109 } 110 e.prefix = prefix 111 e.indentStr = indent 112 e.enabledIndent = true 113 } 114 115 func marshalContext(ctx context.Context, v any, optFuncs ...EncodeOptionFunc) ([]byte, error) { 116 rctx := encoder.TakeRuntimeContext() 117 rctx.Option.Flag = 0 118 rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.ContextOption 119 rctx.Option.Context = ctx 120 for _, optFunc := range optFuncs { 121 optFunc(rctx.Option) 122 } 123 124 buf, err := encode(rctx, v) 125 if err != nil { 126 encoder.ReleaseRuntimeContext(rctx) 127 return nil, err 128 } 129 130 // this line exists to escape call of `runtime.makeslicecopy` . 131 // if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`, 132 // dst buffer size and src buffer size are differrent. 133 // in this case, compiler uses `runtime.makeslicecopy`, but it is slow. 134 buf = buf[:len(buf)-1] 135 copied := make([]byte, len(buf)) 136 copy(copied, buf) 137 138 encoder.ReleaseRuntimeContext(rctx) 139 return copied, nil 140 } 141 142 func marshal(v any, optFuncs ...EncodeOptionFunc) ([]byte, error) { 143 ctx := encoder.TakeRuntimeContext() 144 145 ctx.Option.Flag = 0 146 ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option) 147 for _, optFunc := range optFuncs { 148 optFunc(ctx.Option) 149 } 150 151 buf, err := encode(ctx, v) 152 if err != nil { 153 encoder.ReleaseRuntimeContext(ctx) 154 return nil, err 155 } 156 157 // this line exists to escape call of `runtime.makeslicecopy` . 158 // if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`, 159 // dst buffer size and src buffer size are differrent. 160 // in this case, compiler uses `runtime.makeslicecopy`, but it is slow. 161 buf = buf[:len(buf)-1] 162 copied := make([]byte, len(buf)) 163 copy(copied, buf) 164 165 encoder.ReleaseRuntimeContext(ctx) 166 return copied, nil 167 } 168 169 func marshalNoEscape(v any) ([]byte, error) { 170 ctx := encoder.TakeRuntimeContext() 171 172 ctx.Option.Flag = 0 173 ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option) 174 175 buf, err := encodeNoEscape(ctx, v) 176 if err != nil { 177 encoder.ReleaseRuntimeContext(ctx) 178 return nil, err 179 } 180 181 // this line exists to escape call of `runtime.makeslicecopy` . 182 // if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`, 183 // dst buffer size and src buffer size are differrent. 184 // in this case, compiler uses `runtime.makeslicecopy`, but it is slow. 185 buf = buf[:len(buf)-1] 186 copied := make([]byte, len(buf)) 187 copy(copied, buf) 188 189 encoder.ReleaseRuntimeContext(ctx) 190 return copied, nil 191 } 192 193 func marshalIndent(v any, prefix, indent string, optFuncs ...EncodeOptionFunc) ([]byte, error) { 194 ctx := encoder.TakeRuntimeContext() 195 196 ctx.Option.Flag = 0 197 ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.IndentOption) 198 for _, optFunc := range optFuncs { 199 optFunc(ctx.Option) 200 } 201 202 buf, err := encodeIndent(ctx, v, prefix, indent) 203 if err != nil { 204 encoder.ReleaseRuntimeContext(ctx) 205 return nil, err 206 } 207 208 buf = buf[:len(buf)-2] 209 copied := make([]byte, len(buf)) 210 copy(copied, buf) 211 212 encoder.ReleaseRuntimeContext(ctx) 213 return copied, nil 214 } 215 216 func encode(ctx *encoder.RuntimeContext, v any) ([]byte, error) { 217 b := ctx.Buf[:0] 218 if v == nil { 219 b = encoder.AppendNull(ctx, b) 220 b = encoder.AppendComma(ctx, b) 221 return b, nil 222 } 223 header := (*emptyInterface)(unsafe.Pointer(&v)) 224 typ := header.typ 225 226 typeptr := uintptr(unsafe.Pointer(typ)) 227 codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr) 228 if err != nil { 229 return nil, err 230 } 231 232 p := uintptr(header.ptr) 233 ctx.Init(p, codeSet.CodeLength) 234 ctx.KeepRefs = append(ctx.KeepRefs, header.ptr) 235 236 buf, err := encodeRunCode(ctx, b, codeSet) 237 if err != nil { 238 return nil, err 239 } 240 ctx.Buf = buf 241 return buf, nil 242 } 243 244 func encodeNoEscape(ctx *encoder.RuntimeContext, v any) ([]byte, error) { 245 b := ctx.Buf[:0] 246 if v == nil { 247 b = encoder.AppendNull(ctx, b) 248 b = encoder.AppendComma(ctx, b) 249 return b, nil 250 } 251 header := (*emptyInterface)(unsafe.Pointer(&v)) 252 typ := header.typ 253 254 typeptr := uintptr(unsafe.Pointer(typ)) 255 codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr) 256 if err != nil { 257 return nil, err 258 } 259 260 p := uintptr(header.ptr) 261 ctx.Init(p, codeSet.CodeLength) 262 buf, err := encodeRunCode(ctx, b, codeSet) 263 if err != nil { 264 return nil, err 265 } 266 267 ctx.Buf = buf 268 return buf, nil 269 } 270 271 func encodeIndent(ctx *encoder.RuntimeContext, v any, prefix, indent string) ([]byte, error) { 272 b := ctx.Buf[:0] 273 if v == nil { 274 b = encoder.AppendNull(ctx, b) 275 b = encoder.AppendCommaIndent(ctx, b) 276 return b, nil 277 } 278 header := (*emptyInterface)(unsafe.Pointer(&v)) 279 typ := header.typ 280 281 typeptr := uintptr(unsafe.Pointer(typ)) 282 codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr) 283 if err != nil { 284 return nil, err 285 } 286 287 p := uintptr(header.ptr) 288 ctx.Init(p, codeSet.CodeLength) 289 buf, err := encodeRunIndentCode(ctx, b, codeSet, prefix, indent) 290 291 ctx.KeepRefs = append(ctx.KeepRefs, header.ptr) 292 293 if err != nil { 294 return nil, err 295 } 296 297 ctx.Buf = buf 298 return buf, nil 299 } 300 301 func encodeRunCode(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]byte, error) { 302 if (ctx.Option.Flag & encoder.DebugOption) != 0 { 303 if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { 304 return vm_color.DebugRun(ctx, b, codeSet) 305 } 306 return vm.DebugRun(ctx, b, codeSet) 307 } 308 if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { 309 return vm_color.Run(ctx, b, codeSet) 310 } 311 return vm.Run(ctx, b, codeSet) 312 } 313 314 func encodeRunIndentCode(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, prefix, indent string) ([]byte, error) { 315 ctx.Prefix = unsafeConvert.BytesReflect(prefix) 316 ctx.IndentStr = unsafeConvert.BytesReflect(indent) 317 if (ctx.Option.Flag & encoder.DebugOption) != 0 { 318 if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { 319 return vm_color_indent.DebugRun(ctx, b, codeSet) 320 } 321 return vm_indent.DebugRun(ctx, b, codeSet) 322 } 323 if (ctx.Option.Flag & encoder.ColorizeOption) != 0 { 324 return vm_color_indent.Run(ctx, b, codeSet) 325 } 326 return vm_indent.Run(ctx, b, codeSet) 327 }