package jsonx
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"sort"
"github.com/Jeffail/gabs"
)
const (
XMLHeader = ``
Header = ``
Footer = ``
)
// namedContainer wraps a gabs.Container to carry name information with it
type namedContainer struct {
name string
*gabs.Container
}
// Marshal marshals the input data into JSONx.
func Marshal(input interface{}) (string, error) {
jsonBytes, err := json.Marshal(input)
if err != nil {
return "", err
}
xmlBytes, err := EncodeJSONBytes(jsonBytes)
if err != nil {
return "", err
}
return fmt.Sprintf("%s%s%s%s", XMLHeader, Header, string(xmlBytes), Footer), nil
}
// EncodeJSONBytes encodes JSON-formatted bytes into JSONx. It is designed to
// be used for multiple entries so does not prepend the JSONx header tag or
// append the JSONx footer tag. You can use jsonx.Header and jsonx.Footer to
// easily add these when necessary.
func EncodeJSONBytes(input []byte) ([]byte, error) {
o := bytes.NewBuffer(nil)
reader := bytes.NewReader(input)
dec := json.NewDecoder(reader)
dec.UseNumber()
cont, err := gabs.ParseJSONDecoder(dec)
if err != nil {
return nil, err
}
if err := sortAndTransformObject(o, &namedContainer{Container: cont}); err != nil {
return nil, err
}
return o.Bytes(), nil
}
func transformContainer(o *bytes.Buffer, cont *namedContainer) error {
var printName string
if cont.name != "" {
escapedNameBuf := bytes.NewBuffer(nil)
err := xml.EscapeText(escapedNameBuf, []byte(cont.name))
if err != nil {
return err
}
printName = fmt.Sprintf(" name=\"%s\"", escapedNameBuf.String())
}
data := cont.Data()
switch data.(type) {
case nil:
o.WriteString(fmt.Sprintf("", printName))
case bool:
o.WriteString(fmt.Sprintf("%t", printName, data))
case json.Number:
o.WriteString(fmt.Sprintf("%v", printName, data))
case string:
o.WriteString(fmt.Sprintf("%v", printName, data))
case []interface{}:
o.WriteString(fmt.Sprintf("", printName))
arrayChildren, err := cont.Children()
if err != nil {
return err
}
for _, child := range arrayChildren {
if err := transformContainer(o, &namedContainer{Container: child}); err != nil {
return err
}
}
o.WriteString("")
case map[string]interface{}:
o.WriteString(fmt.Sprintf("", printName))
if err := sortAndTransformObject(o, cont); err != nil {
return err
}
o.WriteString("")
}
return nil
}
// sortAndTransformObject sorts object keys to make the output predictable so
// the package can be tested; logic is here to prevent code duplication
func sortAndTransformObject(o *bytes.Buffer, cont *namedContainer) error {
objectChildren, err := cont.ChildrenMap()
if err != nil {
return err
}
sortedNames := make([]string, 0, len(objectChildren))
for name, _ := range objectChildren {
sortedNames = append(sortedNames, name)
}
sort.Strings(sortedNames)
for _, name := range sortedNames {
if err := transformContainer(o, &namedContainer{name: name, Container: objectChildren[name]}); err != nil {
return err
}
}
return nil
}