I was tasked with writing a Go utility that takes an XML file, parses it, and returns it in JSON.
Here is an example of the XML:
<?xml version="1.0" encoding="utf-8"?>
<tracks clid="020">
<track uuid="551" category="s" route="8" vehicle_type="trolleybus">
<point latitude="53.61491" longitude="55.90922" avg_speed="24" direction="270" time="13122022:072116"/>
</track>
<track uuid="552" category="s" route="6" vehicle_type="trolleybus">
<point latitude="53.68321" longitude="57.90922" avg_speed="42" direction="181" time="13122022:072216"/>
</track>
</tracks>
I wrote the following code:
package main
import (
"encoding/json"
"encoding/xml"
"fmt"
)
type Tracks struct {
XMLName xml.Name `xml:"tracks" json:"-"`
Clid string `xml:"clid,attr" json:"clid"`
Tracks []Track `xml:"track" json:"track_list"`
}
type Track struct {
XMLName xml.Name `xml:"tracks"`
Uuid string `xml:"uuid,attr" json:"uuid"`
Category string `xml:"category,attr" json:"category"`
Route string `xml:"route,attr" json:"route"`
VehicleType string `xml:"vehicle_type,attr" json:"vehicle_type"`
Point Point `xml:"point" json:"point"`
}
type Point struct {
Latitude string `xml:"latitude,attr" json:"latitude"`
Longitude string `xml:"longitude,attr" json:"longitude"`
AvgSpeed string `xml:"avg_speed,attr" json:"avg_speed"`
Direction string `xml:"direction,attr" json:"direction"`
Time string `xml:"time,attr" json:"time"`
}
func main() {
rawXmlData := `
<?xml version="1.0" encoding="utf-8"?>
<tracks clid="020">
<track uuid="551" category="s" route="8" vehicle_type="trolleybus">
<point latitude="53.61491" longitude="55.90922" avg_speed="24" direction="270" time="13122022:072116"/>
</track>
<track uuid="552" category="s" route="6" vehicle_type="trolleybus">
<point latitude="53.68321" longitude="57.90922" avg_speed="42" direction="181" time="13122022:072216"/>
</track>
</tracks>
`
var tracks Tracks
err := xml.Unmarshal([]byte(rawXmlData), &tracks)
if err != nil {
log.Fatal(err)
}
jsonData, err := json.Marshal(tracks)
if err != nil {
log.Fatal(err)
}
fmt.Printf(string(jsonData))
}
But, unfortunately, it doesn't work. I get the following in the console:
2009/11/10 23:00:00 expected element type <tracks> but have <track>
What am I doing wrong? How can I solve this problem?
CodePudding user response:
I thought I would move the discussion to an answer, since I think you're pretty close. As I mentioned, you need to check the error returned by xml.Unmarshal
. That might look like this:
if err := xml.Unmarshal([]byte(rawXmlData), &tracks); err != nil {
panic(err)
}
Now that you have valid XML data in your code, we can produce meaningful errors; with the above error check in place, running your code produces:
panic: expected element type <tracks> but have <track>
goroutine 1 [running]:
main.main()
/home/lars/tmp/go/main.go:48 0x12f
That's happening because of a minor typo in your data structures; in the definition of your Track
struct, you have:
type Track struct {
XMLName xml.Name `xml:"tracks"`
Uuid string `xml:"uuid,attr" json:"uuid"`
Category string `xml:"category,attr" json:"category"`
Route string `xml:"route,attr" json:"route"`
VehicleType string `xml:"vehicle_type,attr" json:"vehicle_type"`
Point Point `xml:"point" json:"point"`
}
You've mis-tagged the XMLName
attribute as tracks
when it should be track
:
type Track struct {
XMLName xml.Name `xml:"track"`
Uuid string `xml:"uuid,attr" json:"uuid"`
Category string `xml:"category,attr" json:"category"`
Route string `xml:"route,attr" json:"route"`
VehicleType string `xml:"vehicle_type,attr" json:"vehicle_type"`
Point Point `xml:"point" json:"point"`
}
Lastly -- and this isn't directly related to the problem -- you should avoid naming a variable error
, because that's the name of the built-in data type of errors. I would modify your call to json.Marshal
like this:
jsonData, err := json.Marshal(tracks)
if err != nil {
panic(err)
}
You don't need to panic()
on errors; this is just a convenient way to bail out of the code.
With these changes in place, if we compile and run the code we get as output (formatted with jq
):
{
"clid": "020",
"track_list": [
{
"XMLName": {
"Space": "",
"Local": "track"
},
"uuid": "551",
"category": "s",
"route": "8",
"vehicle_type": "trolleybus",
"point": {
"latitude": "53.61491",
"longitude": "55.90922",
"avg_speed": "24",
"direction": "270",
"time": "13122022:072116"
}
},
{
"XMLName": {
"Space": "",
"Local": "track"
},
"uuid": "552",
"category": "s",
"route": "6",
"vehicle_type": "trolleybus",
"point": {
"latitude": "53.68321",
"longitude": "57.90922",
"avg_speed": "42",
"direction": "181",
"time": "13122022:072216"
}
}
]
}
Note that you don't even need that XMLName
element in your structure; if we remove it completely so that we have:
type Track struct {
Uuid string `xml:"uuid,attr" json:"uuid"`
Category string `xml:"category,attr" json:"category"`
Route string `xml:"route,attr" json:"route"`
VehicleType string `xml:"vehicle_type,attr" json:"vehicle_type"`
Point Point `xml:"point" json:"point"`
}
Then we get as output (formatted with jq
):
{
"clid": "020",
"track_list": [
{
"uuid": "551",
"category": "s",
"route": "8",
"vehicle_type": "trolleybus",
"point": {
"latitude": "53.61491",
"longitude": "55.90922",
"avg_speed": "24",
"direction": "270",
"time": "13122022:072116"
}
},
{
"uuid": "552",
"category": "s",
"route": "6",
"vehicle_type": "trolleybus",
"point": {
"latitude": "53.68321",
"longitude": "57.90922",
"avg_speed": "42",
"direction": "181",
"time": "13122022:072216"
}
}
]
}
CodePudding user response:
type Track struct {
XMLName xml.Name `xml:"tracks"`
should be "track" not "tracks"