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