1 // Package json2xml converts a JSON structure to XML. 2 // 3 // json2xml wraps each type within xml tags named after the type. For example:- 4 // 5 // An object is wrapped in `<object></object>` 6 // An array is wrapped in `<array></array>` 7 // A boolean is wrapped in `<boolean></boolean>` , with either "true" or "false" as chardata 8 // A number is wrapped in `<number></number>` 9 // A string is wrapped in `<string></string>` 10 // A null becomes `<null></null>`, with no chardata 11 // 12 // When a type is a member of an object, the name of the key becomes an 13 // attribute on the type tag, for example: - 14 // 15 // { 16 // "Location": { 17 // "Longitude": -1.8262, 18 // "Latitude": 51.1789 19 // } 20 // } 21 // 22 // ...becomes... 23 // 24 // `<object> 25 // <object name="Location"> 26 // <number name="Longitude">-1.8262</number> 27 // <number name="Latitude">51.1789</number> 28 // </object> 29 // </object>` 30 package json2xml // import "vimagination.zapto.org/json2xml" 31 32 import ( 33 "encoding/json" 34 "encoding/xml" 35 "errors" 36 "io" 37 "strconv" 38 ) 39 40 type ttype byte 41 42 const ( 43 typObject ttype = iota 44 typArray 45 typBool 46 typNumber 47 typString 48 typNull 49 ) 50 51 var ttypeNames = [...]string{"object", "array", "boolean", "number", "string", "null"} 52 53 // JSONDecoder represents a type that gives out JSON tokens, usually 54 // implemented by *json.Decoder 55 // It is encouraged for implementers of this interface to output numbers using 56 // the json.Number type, as it reduces needless conversions. 57 // Users of the json.Decoder implementation should call the UseNumber method to 58 // achieve this 59 type JSONDecoder interface { 60 Token() (json.Token, error) 61 } 62 63 // XMLEncoder represents a type that takes XML tokens, usually implemented by 64 // *xml.Encoder 65 type XMLEncoder interface { 66 EncodeToken(xml.Token) error 67 } 68 69 // Converter represents the ongoing conversion from JSON to XML 70 type Converter struct { 71 decoder JSONDecoder 72 types []ttype 73 data *string 74 } 75 76 // Tokens provides a JSON converter that implements the xml.TokenReader 77 // interface 78 func Tokens(j JSONDecoder) *Converter { 79 return &Converter{ 80 decoder: j, 81 } 82 } 83 84 // Token gets a xml.Token from the Converter, as per the xml.TokenReader 85 // interface 86 func (c *Converter) Token() (xml.Token, error) { 87 if c.data != nil { 88 token := xml.CharData(*c.data) 89 c.data = nil 90 return token, nil 91 } 92 if len(c.types) > 0 { 93 switch c.types[len(c.types)-1] { 94 case typObject, typArray: 95 default: 96 return c.outputEnd(), nil 97 } 98 } 99 var keyName *string 100 token, err := c.decoder.Token() 101 if err != nil { 102 return nil, err 103 } 104 if len(c.types) > 0 && c.types[len(c.types)-1] == typObject && token != json.Delim('}') { 105 tokenStr, ok := token.(string) 106 if !ok { 107 return nil, ErrInvalidKey 108 } 109 keyName = &tokenStr 110 token, err = c.decoder.Token() 111 if err != nil { 112 return nil, err 113 } 114 } 115 switch token := token.(type) { 116 case json.Delim: 117 switch token { 118 case '{': 119 return c.outputStart(typObject, keyName), nil 120 case '[': 121 return c.outputStart(typArray, keyName), nil 122 case '}': 123 if len(c.types) == 0 || c.types[len(c.types)-1] != typObject { 124 return nil, ErrInvalidToken 125 } 126 return c.outputEnd(), nil 127 case ']': 128 if len(c.types) == 0 || c.types[len(c.types)-1] != typArray { 129 return nil, ErrInvalidToken 130 } 131 return c.outputEnd(), nil 132 default: 133 return nil, ErrUnknownToken 134 } 135 case bool: 136 if token { 137 return c.outputType(typBool, &cTrue, keyName), nil 138 } 139 return c.outputType(typBool, &cFalse, keyName), nil 140 case float64: 141 number := strconv.FormatFloat(token, 'f', -1, 64) 142 return c.outputType(typNumber, &number, keyName), nil 143 case json.Number: 144 return c.outputType(typNumber, (*string)(&token), keyName), nil 145 case string: 146 return c.outputType(typString, &token, keyName), nil 147 case nil: 148 return c.outputType(typNull, nil, keyName), nil 149 default: 150 return nil, ErrUnknownToken 151 } 152 } 153 154 func (c *Converter) outputType(typ ttype, data *string, keyName *string) xml.Token { 155 c.data = data 156 return c.outputStart(typ, keyName) 157 } 158 159 func (c *Converter) outputStart(typ ttype, keyName *string) xml.Token { 160 c.types = append(c.types, typ) 161 var attr []xml.Attr 162 if keyName != nil { 163 attr = []xml.Attr{ 164 { 165 Name: xml.Name{ 166 Local: "name", 167 }, 168 Value: *keyName, 169 }, 170 } 171 } 172 return xml.StartElement{ 173 Name: xml.Name{ 174 Local: ttypeNames[typ], 175 }, 176 Attr: attr, 177 } 178 } 179 180 func (c *Converter) outputEnd() xml.Token { 181 typ := c.types[len(c.types)-1] 182 c.types = c.types[:len(c.types)-1] 183 return xml.EndElement{ 184 Name: xml.Name{ 185 Local: ttypeNames[typ], 186 }, 187 } 188 } 189 190 // Convert converts JSON and sends it to the given XML encoder 191 func Convert(j JSONDecoder, x XMLEncoder) error { 192 c := Converter{ 193 decoder: j, 194 } 195 for { 196 tk, err := c.Token() 197 if err != nil { 198 if err == io.EOF { 199 return nil 200 } 201 return err 202 } 203 if err = x.EncodeToken(tk); err != nil { 204 return err 205 } 206 } 207 } 208 209 var ( 210 cTrue = "true" 211 cFalse = "false" 212 ) 213 214 // Errors 215 var ( 216 ErrInvalidKey = errors.New("invalid key type") 217 ErrUnknownToken = errors.New("unknown token type") 218 ErrInvalidToken = errors.New("invalid token") 219 ) 220