123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- // 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
- }
|