grokify/mogo

View on GitHub
encoding/jsonutil/jsonutil.go

Summary

Maintainability
A
0 mins
Test Coverage
package jsonutil

import (
    "bytes"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "io/fs"
    "os"
    "reflect"

    "github.com/grokify/mogo/type/maputil"
    // jsoniter "github.com/json-iterator/go"
)

// var json = jsoniter.ConfigCompatibleWithStandardLibrary

const FileExt = ".json"

var (
    MarshalPrefix = ""
    MarshalIndent = "    "
)

type mustMarhshalError struct {
    MustMarhshalError string `json:"must_marshal_error"`
}

func MarshalSimple(v any, prefix, indent string) ([]byte, error) {
    if prefix == "" && indent == "" {
        return json.Marshal(v)
    } else {
        return json.MarshalIndent(v, prefix, indent)
    }
}

func MustMarshalSimple(v any, prefix, indent string) []byte {
    if b, err := MarshalSimple(v, prefix, indent); err != nil {
        panic(err)
    } else {
        return b
    }
}

func MustMarshal(v any, embedError bool) []byte {
    bytes, err := json.Marshal(v)
    if err != nil {
        if embedError {
            e := mustMarhshalError{
                MustMarhshalError: err.Error(),
            }
            bytes, err := json.Marshal(e)
            if err != nil {
                panic(err)
            }
            return bytes
        }
        panic(err)
    }
    return bytes
}

func MustMarshalString(v any, embedError bool) string {
    return string(MustMarshal(v, embedError))
}

func MustMarshalIndent(v any, prefix, indent string, embedError bool) []byte {
    if b, err := json.MarshalIndent(v, prefix, indent); err != nil {
        panic(err)
    } else {
        return b
    }
}

// IndentBytes converts a JSON byte array into a prettified byte array.
func IndentBytes(data []byte, prefix, indent string) ([]byte, error) {
    var out bytes.Buffer
    if err := json.Indent(&out, data, prefix, indent); err != nil {
        return []byte{}, err
    } else {
        return out.Bytes(), nil
    }
}

func WriteFileIndentBytes(name string, data []byte, prefix, indent string, perm fs.FileMode) error {
    if data, err := IndentBytes(data, prefix, indent); err != nil {
        return err
    } else {
        return os.WriteFile(name, data, perm)
    }
}

// IndentReader returns a byte slice of indented JSON given an `io.Reader`.
// It is useful to use with `http.Response.Body` which is an `io.ReadCloser`.
func IndentReader(r io.Reader, prefix, indent string) ([]byte, error) {
    if b, err := io.ReadAll(r); err != nil {
        return b, err
    } else {
        return IndentBytes(b, prefix, indent)
    }
}

type unescapeWrap struct {
    Raw string `json:"raw"`
}

// Unescape is designed to unescape a stringified JSON. It is typically used
// after a stringified JSON has been embedded as a value in an wrapper JSON object.
// When using this, do not include outer quotes.
func Unescape(b []byte, prefix, indent string) ([]byte, error) {
    wrapped := fmt.Sprintf("{\"raw\":\"%s\"}", string(b))
    w := &unescapeWrap{}
    if err := json.Unmarshal([]byte(wrapped), w); err != nil {
        return nil, err
    } else if formatted, err := IndentBytes([]byte(w.Raw), prefix, indent); err != nil {
        return nil, err
    } else {
        return formatted, nil
    }
}

func MarshalBase64(v any) (string, error) {
    if data, err := json.Marshal(v); err != nil {
        return "", err
    } else {
        return base64.StdEncoding.EncodeToString(data), nil
    }
}

func MustUnmarshal(b []byte, v any) {
    if err := json.Unmarshal(b, v); err != nil {
        panic(err.Error())
    }
}

// UnmarshalAny will unmarshal anything to `v`, including first marshalling anything
// that is not a byte array to a JSON byte array.
func UnmarshalAny(data, v any) error {
    var err error
    b, ok := data.([]byte)
    if !ok {
        b, err = json.Marshal(data)
        if err != nil {
            return err
        }
    }
    return json.Unmarshal(b, v)
}

func UnmarshalMSI(data map[string]any, v any) error {
    if b, err := json.Marshal(data); err != nil {
        return err
    } else {
        return json.Unmarshal(b, v)
    }
}

func UnmarshalReader(r io.Reader, v any) ([]byte, error) {
    if b, err := io.ReadAll(r); err != nil {
        return b, err
    } else {
        return b, json.Unmarshal(b, v)
    }
}

// UnmarshalStrict returns an error when the destination is a struct and the input contains object keys which do not match any non-ignored, exported fields in the destination.
func UnmarshalStrict(b []byte, v any) error {
    dec := json.NewDecoder(bytes.NewReader(b))
    dec.DisallowUnknownFields()
    return dec.Decode(v)
}

// PrintReaderIndent returns an indented JSON byte array given an `io.Reader`.
func PrintReaderIndent(r io.Reader, prefix, indent string) ([]byte, error) {
    bytes, err := io.ReadAll(r)
    if err != nil {
        return bytes, err
    }
    outBytes, err := IndentBytes(bytes, prefix, indent)
    if err != nil {
        return bytes, err
    }
    _, err = fmt.Println(string(outBytes))
    return outBytes, err
}

func UnmarshalFile(filename string, v any) ([]byte, error) {
    if b, err := os.ReadFile(filename); err != nil {
        return b, err
    } else {
        return b, json.Unmarshal(b, v)
    }
}

func WriteFile(filename string, v any, prefix, indent string, perm fs.FileMode) error {
    if b, err := MarshalSimple(v, prefix, indent); err != nil {
        return err
    } else {
        return os.WriteFile(filename, b, perm)
    }
}

func Equal(x, y io.Reader) (bool, error) {
    var ax, ay any
    d := json.NewDecoder(x)
    if err := d.Decode(&ax); err != nil {
        return false, err
    }
    d = json.NewDecoder(y)
    if err := d.Decode(&ay); err != nil {
        return false, err
    }
    return reflect.DeepEqual(ax, ay), nil
}

func EqualBytes(x, y []byte) (bool, error) {
    var ax, ay any
    if err := json.Unmarshal(x, &ax); err != nil {
        return false, err
    } else if err := json.Unmarshal(y, &ay); err != nil {
        return false, err
    } else {
        return reflect.DeepEqual(ax, ay), nil
    }
}

func EqualFiles(x, y string) (bool, error) {
    fx, err := os.Open(x)
    if err != nil {
        return false, err
    }
    defer fx.Close()
    fy, err := os.Open(y)
    if err != nil {
        return false, err
    }
    defer fy.Close()
    return Equal(fx, fy)
}

func UnmarshalKeys(b []byte) ([]string, error) {
    msa := map[string]any{}
    if err := json.Unmarshal(b, &msa); err != nil {
        return []string{}, err
    } else {
        return maputil.Keys(msa), nil
    }
}

func UnmarshalKeysFile(filename string) ([]string, error) {
    if b, err := os.ReadFile(filename); err != nil {
        return []string{}, err
    } else {
        return UnmarshalKeys(b)
    }
}