// Copyright (C) 2022-2023 Luke Shumaker // // SPDX-License-Identifier: GPL-2.0-or-later // Package json is a wrapper around lowmemjson that is a (mostly) // drop-in replacement for the standard library's encoding/json. package json import ( "bufio" "bytes" "encoding/json" "errors" "io" "strconv" "git.lukeshu.com/go/lowmemjson" ) //nolint:stylecheck // ST1021 False positive; these aren't comments on individual types. type ( Number = json.Number RawMessage = json.RawMessage Marshaler = json.Marshaler Unmarshaler = json.Unmarshaler // low-level decode errors. UnmarshalTypeError = json.UnmarshalTypeError // SyntaxError = json.SyntaxError // Duplicated to access a private field. // high-level decode errors. InvalidUnmarshalError = json.InvalidUnmarshalError // marshal errors. UnsupportedTypeError = json.UnsupportedTypeError UnsupportedValueError = json.UnsupportedValueError // MarshalerError = json.MarshalerError // Duplicated to access a private field. ) // Encode wrappers /////////////////////////////////////////////////// func convertEncodeError(err error) error { if me, ok := err.(*lowmemjson.EncodeMethodError); ok { err = &MarshalerError{ Type: me.Type, Err: me.Err, sourceFunc: me.SourceFunc, } } return err } func marshal(v any, cfg lowmemjson.ReEncoderConfig) ([]byte, error) { var buf bytes.Buffer if err := convertEncodeError(lowmemjson.NewEncoder(lowmemjson.NewReEncoder(&buf, cfg)).Encode(v)); err != nil { return nil, err } return buf.Bytes(), nil } func MarshalIndent(v any, prefix, indent string) ([]byte, error) { return marshal(v, lowmemjson.ReEncoderConfig{ Indent: indent, Prefix: prefix, }) } func Marshal(v any) ([]byte, error) { return marshal(v, lowmemjson.ReEncoderConfig{ Compact: true, }) } type Encoder struct { out io.Writer buf bytes.Buffer cfg lowmemjson.ReEncoderConfig encoder *lowmemjson.Encoder formatter *lowmemjson.ReEncoder } func NewEncoder(w io.Writer) *Encoder { ret := &Encoder{ out: w, cfg: lowmemjson.ReEncoderConfig{ AllowMultipleValues: true, Compact: true, ForceTrailingNewlines: true, }, } ret.refreshConfig() return ret } func (enc *Encoder) refreshConfig() { enc.formatter = lowmemjson.NewReEncoder(&enc.buf, enc.cfg) enc.encoder = lowmemjson.NewEncoder(enc.formatter) } func (enc *Encoder) Encode(v any) error { if err := convertEncodeError(enc.encoder.Encode(v)); err != nil { enc.buf.Reset() return err } if _, err := enc.buf.WriteTo(enc.out); err != nil { return err } return nil } func (enc *Encoder) SetEscapeHTML(on bool) { if on { enc.cfg.BackslashEscape = lowmemjson.EscapeDefault } else { enc.cfg.BackslashEscape = lowmemjson.EscapeDefaultNonHTMLSafe } enc.refreshConfig() } func (enc *Encoder) SetIndent(prefix, indent string) { enc.cfg.Compact = prefix == "" && indent == "" enc.cfg.Prefix = prefix enc.cfg.Indent = indent enc.refreshConfig() } // ReEncode wrappers ///////////////////////////////////////////////// func convertReEncodeError(err error) error { if se, ok := err.(*lowmemjson.ReEncodeSyntaxError); ok { err = &SyntaxError{ msg: se.Err.Error(), Offset: se.Offset + 1, } if errors.Is(se.Err, io.ErrUnexpectedEOF) { err.(*SyntaxError).msg = "unexpected end of JSON input" } } return err } func HTMLEscape(dst *bytes.Buffer, src []byte) { _, _ = lowmemjson.NewReEncoder(dst, lowmemjson.ReEncoderConfig{}).Write(src) } func reencode(dst io.Writer, src []byte, cfg lowmemjson.ReEncoderConfig) error { formatter := lowmemjson.NewReEncoder(dst, cfg) _, err := formatter.Write(src) if err == nil { err = formatter.Close() } return convertReEncodeError(err) } func Compact(dst *bytes.Buffer, src []byte) error { start := dst.Len() err := reencode(dst, src, lowmemjson.ReEncoderConfig{ Compact: true, InvalidUTF8: lowmemjson.InvalidUTF8Preserve, BackslashEscape: lowmemjson.EscapePreserve, }) if err != nil { dst.Truncate(start) } return err } func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { start := dst.Len() err := reencode(dst, src, lowmemjson.ReEncoderConfig{ Indent: indent, Prefix: prefix, InvalidUTF8: lowmemjson.InvalidUTF8Preserve, BackslashEscape: lowmemjson.EscapePreserve, }) if err != nil { dst.Truncate(start) } return err } func Valid(data []byte) bool { formatter := lowmemjson.NewReEncoder(io.Discard, lowmemjson.ReEncoderConfig{ Compact: true, InvalidUTF8: lowmemjson.InvalidUTF8Error, }) if _, err := formatter.Write(data); err != nil { return false } if err := formatter.Close(); err != nil { return false } return true } // Decode wrappers /////////////////////////////////////////////////// func convertDecodeError(err error) error { if derr, ok := err.(*lowmemjson.DecodeError); ok { switch terr := derr.Err.(type) { case *lowmemjson.DecodeSyntaxError: switch { case errors.Is(terr.Err, io.EOF): err = io.EOF case errors.Is(terr.Err, io.ErrUnexpectedEOF): err = &SyntaxError{ msg: "unexpected end of JSON input", Offset: terr.Offset, } default: err = &SyntaxError{ msg: terr.Err.Error(), Offset: terr.Offset + 1, } } case *lowmemjson.DecodeTypeError: if typeErr, ok := terr.Err.(*json.UnmarshalTypeError); ok { err = &UnmarshalTypeError{ Value: typeErr.Value, Type: typeErr.Type, Offset: typeErr.Offset, Struct: derr.FieldParent, Field: derr.FieldName, } } else if _, isArgErr := terr.Err.(*lowmemjson.DecodeArgumentError); terr.Err != nil && !isArgErr && !errors.Is(terr.Err, lowmemjson.ErrDecodeNonEmptyInterface) && !errors.Is(terr.Err, strconv.ErrSyntax) && !errors.Is(terr.Err, strconv.ErrRange) { err = terr.Err } else { err = &UnmarshalTypeError{ Value: terr.JSONType, Type: terr.GoType, Offset: terr.Offset, Struct: derr.FieldParent, Field: derr.FieldName, } } } } return err } func Unmarshal(data []byte, ptr any) error { return convertDecodeError(lowmemjson.NewDecoder(bytes.NewReader(data)).DecodeThenEOF(ptr)) } type Decoder struct { *lowmemjson.Decoder buf *bufio.Reader } func NewDecoder(r io.Reader) *Decoder { br, ok := r.(*bufio.Reader) if !ok { br = bufio.NewReader(r) } return &Decoder{ Decoder: lowmemjson.NewDecoder(br), buf: br, } } func (dec *Decoder) Decode(ptr any) error { return convertDecodeError(dec.Decoder.Decode(ptr)) } func (dec *Decoder) Buffered() io.Reader { dat, _ := dec.buf.Peek(dec.buf.Buffered()) return bytes.NewReader(dat) } // func (dec *Decoder) Token() (Token, error)