324 lines
10 KiB
Go
324 lines
10 KiB
Go
// Copyright 2012-2014 Charles Banning. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file
|
|
|
|
package mxj
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// ------------------------------ write JSON -----------------------
|
|
|
|
// Just a wrapper on json.Marshal.
|
|
// If option safeEncoding is'true' then safe encoding of '<', '>' and '&'
|
|
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
|
|
func (mv Map) Json(safeEncoding ...bool) ([]byte, error) {
|
|
var s bool
|
|
if len(safeEncoding) == 1 {
|
|
s = safeEncoding[0]
|
|
}
|
|
|
|
b, err := json.Marshal(mv)
|
|
|
|
if !s {
|
|
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
|
|
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
|
|
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
// Just a wrapper on json.MarshalIndent.
|
|
// If option safeEncoding is'true' then safe encoding of '<' , '>' and '&'
|
|
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
|
|
func (mv Map) JsonIndent(prefix, indent string, safeEncoding ...bool) ([]byte, error) {
|
|
var s bool
|
|
if len(safeEncoding) == 1 {
|
|
s = safeEncoding[0]
|
|
}
|
|
|
|
b, err := json.MarshalIndent(mv, prefix, indent)
|
|
if !s {
|
|
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
|
|
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
|
|
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
// The following implementation is provided for symmetry with NewMapJsonReader[Raw]
|
|
// The names will also provide a key for the number of return arguments.
|
|
|
|
// Writes the Map as JSON on the Writer.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonWriter(jsonWriter io.Writer, safeEncoding ...bool) error {
|
|
b, err := mv.Json(safeEncoding...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return err
|
|
}
|
|
|
|
// Writes the Map as JSON on the Writer. []byte is the raw JSON that was written.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonWriterRaw(jsonWriter io.Writer, safeEncoding ...bool) ([]byte, error) {
|
|
b, err := mv.Json(safeEncoding...)
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return b, err
|
|
}
|
|
|
|
// Writes the Map as pretty JSON on the Writer.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonIndentWriter(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) error {
|
|
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return err
|
|
}
|
|
|
|
// Writes the Map as pretty JSON on the Writer. []byte is the raw JSON that was written.
|
|
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
|
|
func (mv Map) JsonIndentWriterRaw(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) ([]byte, error) {
|
|
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
|
|
_, err = jsonWriter.Write(b)
|
|
return b, err
|
|
}
|
|
|
|
// --------------------------- read JSON -----------------------------
|
|
|
|
// Decode numericvalues as json.Number type Map values - see encoding/json#Number.
|
|
// NOTE: this is for decoding JSON into a Map with NewMapJson(), NewMapJsonReader(),
|
|
// etc.; it does not affect NewMapXml(), etc. The XML encoders mv.Xml() and mv.XmlIndent()
|
|
// do recognize json.Number types; a JSON object can be decoded to a Map with json.Number
|
|
// value types and the resulting Map can be correctly encoded into a XML object.
|
|
var JsonUseNumber bool
|
|
|
|
// Just a wrapper on json.Unmarshal
|
|
// Converting JSON to XML is a simple as:
|
|
// ...
|
|
// mapVal, merr := mxj.NewMapJson(jsonVal)
|
|
// if merr != nil {
|
|
// // handle error
|
|
// }
|
|
// xmlVal, xerr := mapVal.Xml()
|
|
// if xerr != nil {
|
|
// // handle error
|
|
// }
|
|
// NOTE: as a special case, passing a list, e.g., [{"some-null-value":"", "a-non-null-value":"bar"}],
|
|
// will be interpreted as having the root key 'object' prepended - {"object":[ ... ]} - to unmarshal to a Map.
|
|
// See mxj/j2x/j2x_test.go.
|
|
func NewMapJson(jsonVal []byte) (Map, error) {
|
|
// empty or nil begets empty
|
|
if len(jsonVal) == 0 {
|
|
m := make(map[string]interface{}, 0)
|
|
return m, nil
|
|
}
|
|
// handle a goofy case ...
|
|
if jsonVal[0] == '[' {
|
|
jsonVal = []byte(`{"object":` + string(jsonVal) + `}`)
|
|
}
|
|
m := make(map[string]interface{})
|
|
// err := json.Unmarshal(jsonVal, &m)
|
|
buf := bytes.NewReader(jsonVal)
|
|
dec := json.NewDecoder(buf)
|
|
if JsonUseNumber {
|
|
dec.UseNumber()
|
|
}
|
|
err := dec.Decode(&m)
|
|
return m, err
|
|
}
|
|
|
|
// Retrieve a Map value from an io.Reader.
|
|
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
|
|
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
|
|
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
|
|
// a JSON object.
|
|
func NewMapJsonReader(jsonReader io.Reader) (Map, error) {
|
|
jb, err := getJson(jsonReader)
|
|
if err != nil || len(*jb) == 0 {
|
|
return nil, err
|
|
}
|
|
|
|
// Unmarshal the 'presumed' JSON string
|
|
return NewMapJson(*jb)
|
|
}
|
|
|
|
// Retrieve a Map value and raw JSON - []byte - from an io.Reader.
|
|
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
|
|
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
|
|
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
|
|
// a JSON object and retrieve the raw JSON in a single call.
|
|
func NewMapJsonReaderRaw(jsonReader io.Reader) (Map, []byte, error) {
|
|
jb, err := getJson(jsonReader)
|
|
if err != nil || len(*jb) == 0 {
|
|
return nil, *jb, err
|
|
}
|
|
|
|
// Unmarshal the 'presumed' JSON string
|
|
m, merr := NewMapJson(*jb)
|
|
return m, *jb, merr
|
|
}
|
|
|
|
// Pull the next JSON string off the stream: just read from first '{' to its closing '}'.
|
|
// Returning a pointer to the slice saves 16 bytes - maybe unnecessary, but internal to package.
|
|
func getJson(rdr io.Reader) (*[]byte, error) {
|
|
bval := make([]byte, 1)
|
|
jb := make([]byte, 0)
|
|
var inQuote, inJson bool
|
|
var parenCnt int
|
|
var previous byte
|
|
|
|
// scan the input for a matched set of {...}
|
|
// json.Unmarshal will handle syntax checking.
|
|
for {
|
|
_, err := rdr.Read(bval)
|
|
if err != nil {
|
|
if err == io.EOF && inJson && parenCnt > 0 {
|
|
return &jb, fmt.Errorf("no closing } for JSON string: %s", string(jb))
|
|
}
|
|
return &jb, err
|
|
}
|
|
switch bval[0] {
|
|
case '{':
|
|
if !inQuote {
|
|
parenCnt++
|
|
inJson = true
|
|
}
|
|
case '}':
|
|
if !inQuote {
|
|
parenCnt--
|
|
}
|
|
if parenCnt < 0 {
|
|
return nil, fmt.Errorf("closing } without opening {: %s", string(jb))
|
|
}
|
|
case '"':
|
|
if inQuote {
|
|
if previous == '\\' {
|
|
break
|
|
}
|
|
inQuote = false
|
|
} else {
|
|
inQuote = true
|
|
}
|
|
case '\n', '\r', '\t', ' ':
|
|
if !inQuote {
|
|
continue
|
|
}
|
|
}
|
|
if inJson {
|
|
jb = append(jb, bval[0])
|
|
if parenCnt == 0 {
|
|
break
|
|
}
|
|
}
|
|
previous = bval[0]
|
|
}
|
|
|
|
return &jb, nil
|
|
}
|
|
|
|
// ------------------------------- JSON Reader handler via Map values -----------------------
|
|
|
|
// Default poll delay to keep Handler from spinning on an open stream
|
|
// like sitting on os.Stdin waiting for imput.
|
|
var jhandlerPollInterval = time.Duration(1e6)
|
|
|
|
// While unnecessary, we make HandleJsonReader() have the same signature as HandleXmlReader().
|
|
// This avoids treating one or other as a special case and discussing the underlying stdlib logic.
|
|
|
|
// Bulk process JSON using handlers that process a Map value.
|
|
// 'rdr' is an io.Reader for the JSON (stream).
|
|
// 'mapHandler' is the Map processing handler. Return of 'false' stops io.Reader processing.
|
|
// 'errHandler' is the error processor. Return of 'false' stops io.Reader processing and returns the error.
|
|
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
|
|
// This means that you can stop reading the file on error or after processing a particular message.
|
|
// To have reading and handling run concurrently, pass argument to a go routine in handler and return 'true'.
|
|
func HandleJsonReader(jsonReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error {
|
|
var n int
|
|
for {
|
|
m, merr := NewMapJsonReader(jsonReader)
|
|
n++
|
|
|
|
// handle error condition with errhandler
|
|
if merr != nil && merr != io.EOF {
|
|
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
|
|
if ok := errHandler(merr); !ok {
|
|
// caused reader termination
|
|
return merr
|
|
}
|
|
continue
|
|
}
|
|
|
|
// pass to maphandler
|
|
if len(m) != 0 {
|
|
if ok := mapHandler(m); !ok {
|
|
break
|
|
}
|
|
} else if merr != io.EOF {
|
|
<-time.After(jhandlerPollInterval)
|
|
}
|
|
|
|
if merr == io.EOF {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Bulk process JSON using handlers that process a Map value and the raw JSON.
|
|
// 'rdr' is an io.Reader for the JSON (stream).
|
|
// 'mapHandler' is the Map and raw JSON - []byte - processor. Return of 'false' stops io.Reader processing.
|
|
// 'errHandler' is the error and raw JSON processor. Return of 'false' stops io.Reader processing and returns the error.
|
|
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
|
|
// This means that you can stop reading the file on error or after processing a particular message.
|
|
// To have reading and handling run concurrently, pass argument(s) to a go routine in handler and return 'true'.
|
|
func HandleJsonReaderRaw(jsonReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error {
|
|
var n int
|
|
for {
|
|
m, raw, merr := NewMapJsonReaderRaw(jsonReader)
|
|
n++
|
|
|
|
// handle error condition with errhandler
|
|
if merr != nil && merr != io.EOF {
|
|
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
|
|
if ok := errHandler(merr, raw); !ok {
|
|
// caused reader termination
|
|
return merr
|
|
}
|
|
continue
|
|
}
|
|
|
|
// pass to maphandler
|
|
if len(m) != 0 {
|
|
if ok := mapHandler(m, raw); !ok {
|
|
break
|
|
}
|
|
} else if merr != io.EOF {
|
|
<-time.After(jhandlerPollInterval)
|
|
}
|
|
|
|
if merr == io.EOF {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|