// ------------------------------------------------------------------------ // Project atila // Active Thing (activething.com) git.activething.com/go // // File name builder.go // Created by DEV // Modified 18/01/2024 // // Copyright 2024 activething.com // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ------------------------------------------------------------------------ package _pjson import ( "activething.com/go/gogo/core/bins" "activething.com/go/gogo/core/errs" "activething.com/go/gogo/core/opts" "activething.com/go/gogo/packs" "encoding/base64" "encoding/json" "math" "math/cmplx" "time" "unicode/utf8" ) type ( Builder struct { options Options encoder *json.Encoder dateLayout string scratch [64]byte state packs.BuilderState } ) func NewBuilder (options ...opts.OptionFnc[Builder]) *Builder { return opts.Apply[Builder](&Builder{ state : *packs.NewBuilderState(), dateLayout: time.RFC822Z, options : JSONOptionsQuote | JSONOptionsEscapeHTML, }, options ...) } func (p *Builder) Data () []byte { return p.state.Bytes() } func (p *Builder) Error() error { return p.state.Error } func (p *Builder) SetError (err error) { p.state.SetError(err) } func (p *Builder) Reset() { p.encoder = nil p.state.Reset() } func (p *Builder) Any (value any) { switch value.(type) { case json.Marshaler : p.state.SetError(p.jsonEncoder().Encode(value)) default: packs.BuildAny(p ,value,func(v any) error { return p.jsonEncoder().Encode(value) }) } } func (p *Builder) Binary (value []byte) { if value == nil { p.state.Write(G.nullValue) return } if len(value) == 0{ p.state.Write(G.stringEmpty) return } p.state.WriteByte('"') ln := base64.StdEncoding.EncodedLen(len(value)) if ln <= len(p.scratch) { dst := p.scratch[:ln] base64.StdEncoding.Encode(dst, value) p.state.Write(dst) } else if ln <= 1024 { dst := make([]byte, ln) base64.StdEncoding.Encode(dst, value) p.state.Write(dst) } else { enc := base64.NewEncoder(base64.StdEncoding, p.state) enc.Write(value) enc.Close() } p.state.WriteByte('"') } func (p *Builder) String(value string) { if !p.state.SetValue() { panic(p.state.Error) } if len(value) == 0 { p.state.Write(G.stringEmpty) } else { p.string(value,p.options.EscapeHTML()) } return } func (p *Builder) Bool (value bool) { if !p.state.SetValue() { panic(p.state.Error) } if value { p.state.Write(G.trueValue) } else { p.state.Write(G.falseValue) } } func (p *Builder) Rune (value rune) { p.UInt64(uint64(value)) } func (p *Builder) UInt(value uint) { p.UInt64(uint64(value)) } func (p *Builder) UInt8(value uint8) { p.UInt64(uint64(value)) } func (p *Builder) UInt16(value uint16) { p.UInt64(uint64(value)) } func (p *Builder) UInt32(value uint32) { p.UInt64(uint64(value)) } func (p *Builder) UInt64(value uint64) { if !p.state.SetValue() { panic(p.state.Error) } if value == 0 { p.state.WriteByte('0') } else { p.state.WriteUIntStr(value, 10) } } func (p *Builder) Int (value int) { p.Int64(int64(value)) } func (p *Builder) Int8 (value int8) { p.Int64(int64(value)) } func (p *Builder) Int16(value int16) { p.Int64(int64(value)) } func (p *Builder) Int32(value int32) { p.Int64(int64(value)) } func (p *Builder) Int64(value int64) { if !p.state.SetValue() { panic(p.state.Error) } if value == 0 { if p.options.IgnoreDefaults() && p.state.Node.Kind == packs.NodeKindObject { p.state.Truncate(p.state.Len()) return } p.state.WriteByte('0') } else { p.state.WriteIntStr(value, 10) } } func (p *Builder) Float32(value float32) { p.Float64(float64(value)) } func (p *Builder) Float64(value float64) { if !p.state.SetValue() { panic(p.state.Error) } if p.options.QuoteFloats() { p.state.WriteByte('"') } switch { case value == 0 : p.state.Write(G.zeroValue) case math.IsNaN(value) : p.state.Write(G.floatNaN) case math.IsInf(value, 1) : p.state.Write(G.floatInfPos) case math.IsInf(value, -1) : p.state.Write(G.floatInfNeg) default: p.state.WriteFloatStr(value, 'f', -1) } if p.options.QuoteFloats() { p.state.WriteByte('"') } } func (p *Builder) Complex64(value complex64) { p.Complex128(complex128(value)) } func (p *Builder) Complex128(value complex128){ if !p.state.SetValue() { panic(p.state.Error) } if p.options.QuoteFloats() { p.state.WriteByte('"') } switch { case value == 0 : p.state.Write(G.zeroValue) case cmplx.IsInf(value): p.state.Write(G.floatInfPos) case cmplx.IsNaN(value): p.state.Write(G.floatNaN) default: r, i := real(value), imag(value) p.state.WriteByte('"') p.state.WriteFloatStr(r, 'f', -1) p.state.WriteByte('+') p.state.WriteFloatStr(i, 'f', -1) p.state.Write2Byte('i', '"') } if p.options.QuoteFloats() { p.state.WriteByte('"') } } func (p *Builder) Date (value time.Time) { if !p.state.SetValue() { panic(p.state.Error) } p.state.WriteByte('"') p.state.AppendWith(bins.AppendFnc(func(d []byte) []byte { return value.AppendFormat(d,p.dateLayout) })) p.state.WriteByte('"') } func (p *Builder) List(buildFnc func(builder packs.BuilderList)) { p.packCompound(packs.NodeKindList, '[', ']', func() { buildFnc(p) }) } func (p *Builder) Map(buildFnc func(builder packs.BuilderMap)) { p.packCompound(packs.NodeKindMap, '{', '}', func() { buildFnc(p) }) } func (p *Builder) Object(buildFnc func(builder packs.BuilderObject)) { p.packCompound(packs.NodeKindObject, '{', '}', func() { buildFnc(p) }) } func (p *Builder) packCompound(kind packs.NodeKind, opening, closing byte, buildFnc func()) { dn := false defer func(d *bool) { if !(*d) { p.state.SetError(errs.ToError(recover())) } }(&dn) if p.open(kind, opening) { buildFnc() p.close(closing) } dn = true } func (p *Builder) string(s string, scapeHTML bool) { p.state.WriteByte('"') start := 0 for i := 0; i < len(s); { if b := s[i]; b < utf8.RuneSelf { if !G.jsonChars[b].isHtmlUnsafe() || (!scapeHTML && !G.jsonChars[b].isUnsafe()) { i++ continue } if start < i { p.state.WriteString(s[start:i]) } p.state.WriteByte('\\') switch b { case '\\', '"': p.state.WriteByte(b) case '\n': p.state.WriteByte('n') case '\r': p.state.WriteByte('r') case '\t': p.state.WriteByte('t') default: p.state.WriteString(`u00`) p.state.Write2Byte(G.hex[b>>4], G.hex[b&0xF]) } i++ start = i continue } c, size := utf8.DecodeRuneInString(s[i:]) if c == utf8.RuneError && size == 1 { if start < i { p.state.WriteString(s[start:i]) } p.state.WriteString(`\ufffd`) i += size start = i continue } if c == '\u2028' || c == '\u2029' { if start < i { p.state.WriteString(s[start:i]) } p.state.WriteString(`\u202`) p.state.WriteByte(G.hex[c&0xF]) i += size start = i continue } i += size } if start < len(s) { p.state.WriteString(s[start:]) } p.state.WriteByte('"') } func (p *Builder) throwPanicIfError() { if p.state.Error != nil { panic(p.state.Error) } } func (p *Builder) jsonEncoder () *json.Encoder { if p.encoder == nil { p.encoder = json.NewEncoder(p.state) p.encoder.SetEscapeHTML(p.options.EscapeHTML()) } return p.encoder } func (p *Builder) open(kind packs.NodeKind, opening byte) bool { if p.state.Open(kind) { p.addSep() p.state.WriteByte(opening) return true } return false } func (p *Builder) close(closing byte) bool { if p.state.Close() { p.state.WriteByte(closing) return true } return false }