internal/formatter/json.go (70 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package formatter
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"github.com/Masterminds/semver/v3"
)
type JSONFormatter interface {
Format([]byte) ([]byte, bool, error)
Encode(doc any) ([]byte, error)
}
func JSONFormatterBuilder(specVersion semver.Version) JSONFormatter {
if specVersion.LessThan(semver.MustParse("2.12.0")) {
return &jsonFormatterWithHTMLEncoding{}
}
return &jsonFormatter{}
}
// jsonFormatterWithHTMLEncoding function is responsible for formatting the given JSON input.
// It encodes special HTML characters.
type jsonFormatterWithHTMLEncoding struct{}
func (jsonFormatterWithHTMLEncoding) Format(content []byte) ([]byte, bool, error) {
var rawMessage json.RawMessage
err := json.Unmarshal(content, &rawMessage)
if err != nil {
return nil, false, fmt.Errorf("unmarshalling JSON file failed: %w", err)
}
formatted, err := json.MarshalIndent(&rawMessage, "", " ")
if err != nil {
return nil, false, fmt.Errorf("marshalling JSON raw message failed: %w", err)
}
return formatted, string(content) == string(formatted), nil
}
func (jsonFormatterWithHTMLEncoding) Encode(doc any) ([]byte, error) {
return json.MarshalIndent(doc, "", " ")
}
// jsonFormatter function is responsible for formatting the given JSON input.
type jsonFormatter struct{}
func (jsonFormatter) Format(content []byte) ([]byte, bool, error) {
var formatted bytes.Buffer
err := json.Indent(&formatted, content, "", " ")
if err != nil {
return nil, false, fmt.Errorf("formatting JSON document failed: %w", err)
}
return formatted.Bytes(), bytes.Equal(content, formatted.Bytes()), nil
}
func (jsonFormatter) Encode(doc any) ([]byte, error) {
var formatted bytes.Buffer
enc := json.NewEncoder(&formatted)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
err := enc.Encode(doc)
if err != nil {
return nil, err
}
// Trimming to be consistent with MarshalIndent, that seems to trim the result.
return bytes.TrimSpace(formatted.Bytes()), nil
}
// JSONUnmarshalUsingNumber is a drop-in replacement for json.Unmarshal that
// does not default to unmarshaling numeric values to float64 in order to
// prevent low bit truncation of values greater than 1<<53.
// See https://golang.org/cl/6202068 for details.
func JSONUnmarshalUsingNumber(data []byte, v interface{}) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
err := dec.Decode(v)
if err != nil {
if err == io.EOF {
return errors.New("unexpected end of JSON input")
}
return err
}
// Make sure there is no more data after the message
// to approximate json.Unmarshal's behaviour.
if dec.More() {
return fmt.Errorf("more data after top-level value")
}
return nil
}