Unmarshal SOAP message with Go - go

I'm relatively new to the go language.
I have a problem trying to unmarshal a SOAP message. My attempt is to abstract the content of the Body element and avoid defining the XML structure statically, as it changes depending on the requested action.
Unfortunately I can't find a way to do this correctly. In the example, the GetContent function should receive a pointer to the struct that contains the content and add it dynamically to the Body, in order to be filled. But the result is not the expected one.
package main
import (
"encoding/xml"
"fmt"
)
type Message interface{}
type EnvelopeResponse struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Body Message `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
}
type Body struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
Fault *Fault `xml:",omitempty"`
Content Message `xml:",omitempty"`
SOAPBodyContentType string `xml:"-"`
}
type Fault struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"`
Code string `xml:"faultcode,omitempty"`
String string `xml:"faultstring,omitempty"`
Actor string `xml:"faultactor,omitempty"`
Detail string `xml:"detail,omitempty"`
}
type GetHostNumberOfEntriesResponse struct {
XMLName xml.Name `xml:"urn:dslforum-org:service:Hosts:1 GetHostNumberOfEntriesResponse"`
NewHostNumberOfEntries int64 `xml:"NewHostNumberOfEntries"`
}
func GetContent(rawXml []byte, content interface{}) {
envelope := EnvelopeResponse{Body: Body{Content: content}}
xml.Unmarshal(rawXml, &envelope)
}
func main() {
b := []byte(`
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetHostNumberOfEntriesResponse xmlns:u="urn:dslforum-org:service:Hosts:1">
<NewHostNumberOfEntries>47</NewHostNumberOfEntries>
</u:GetHostNumberOfEntriesResponse>
</s:Body>
</s:Envelope>
`)
content := &GetHostNumberOfEntriesResponse{}
GetContent(b, content)
fmt.Println(*content)
}
Here the example in the playground:
https://go.dev/play/p/BBR4vEXiPbc

you use some interface{}, Why? Follow is test pass code:
https://go.dev/play/p/OPkNScHjri1
package main
import (
"encoding/xml"
"fmt"
)
type Message interface{}
type EnvelopeResponse struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Body Body
}
type Body struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
GetHostNumberOfEntriesResponse GetHostNumberOfEntriesResponse
Fault *Fault `xml:",omitempty"`
Content Message `xml:",omitempty"`
SOAPBodyContentType string `xml:"-"`
}
type Fault struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"`
Code string `xml:"faultcode,omitempty"`
String string `xml:"faultstring,omitempty"`
Actor string `xml:"faultactor,omitempty"`
Detail string `xml:"detail,omitempty"`
}
type GetHostNumberOfEntriesResponse struct {
XMLName xml.Name `xml:"urn:dslforum-org:service:Hosts:1 GetHostNumberOfEntriesResponse"`
NewHostNumberOfEntries int64 `xml:"NewHostNumberOfEntries"`
}
func GetContent(rawXml []byte) *EnvelopeResponse {
envelope := EnvelopeResponse{}
xml.Unmarshal(rawXml, &envelope)
return &envelope
}
func main() {
b := []byte(`
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetHostNumberOfEntriesResponse xmlns:u="urn:dslforum-org:service:Hosts:1">
<NewHostNumberOfEntries>47</NewHostNumberOfEntries>
</u:GetHostNumberOfEntriesResponse>
</s:Body>
</s:Envelope>
`)
envelope := GetContent(b)
fmt.Println(envelope)
}
&{{http://schemas.xmlsoap.org/soap/envelope/ Envelope} {{http://schemas.xmlsoap.org/soap/envel
ope/ Body} {{urn:dslforum-org:service:Hosts:1 GetHostNumberOfEntriesResponse} 47} <nil> <nil>
}}

The solution I found is to use generics to represent the variable and parameterized content of the body.
This code works as I expect:
package main
import (
"encoding/xml"
"fmt"
)
type EnvelopeResponse[T any] struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Body Body[T] `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
}
type Body[T any] struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
Fault *Fault `xml:",omitempty"`
Content T `xml:",omitempty"`
SOAPBodyContentType string `xml:"-"`
}
type Fault struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"`
Code string `xml:"faultcode,omitempty"`
String string `xml:"faultstring,omitempty"`
Actor string `xml:"faultactor,omitempty"`
Detail string `xml:"detail,omitempty"`
}
type GetHostNumberOfEntriesResponse struct {
XMLName xml.Name `xml:"urn:dslforum-org:service:Hosts:1 GetHostNumberOfEntriesResponse"`
NewHostNumberOfEntries int64 `xml:"NewHostNumberOfEntries"`
}
func GetContent[T any](rawXml []byte, content T) {
envelope := EnvelopeResponse[T]{Body: Body[T]{Content: content}}
xml.Unmarshal(rawXml, &envelope)
}
func main() {
b := []byte(`
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetHostNumberOfEntriesResponse xmlns:u="urn:dslforum-org:service:Hosts:1">
<NewHostNumberOfEntries>47</NewHostNumberOfEntries>
</u:GetHostNumberOfEntriesResponse>
</s:Body>
</s:Envelope>
`)
content := &GetHostNumberOfEntriesResponse{}
GetContent(b, content)
fmt.Println(*content)
}

Related

Why simple XML parser doesn't fill struct

Trying to implement a simple XML parsing, the code below doesn't work as expected.
It just returns a {[]} empty Results, while it should fill it.
Why ?...
package main
import "fmt"
import "encoding/xml"
import "bytes"
type Name struct {
Name string `xml:"NAME"`
}
type Results struct {
Names []Name `xml:"RESULTS"`
}
func main() {
data := []byte(`
<?xml version="1.0" encoding="UTF-8"?>
<RESULTS>
<NAME>Apple</NAME>
<NAME>Banana</NAME>
</RESULTS>
`)
var r Results
decoder := xml.NewDecoder(bytes.NewBuffer(data))
unError := decoder.Decode(&r)
if unError != nil {
fmt.Println("XML Unmarshaling error:", unError )
}else{
fmt.Printf("%v", r)
}
}
Tryed in the Playground, and locally (go1.17.2).
I would suggest you to use a online struct generator like xmltogo, so use this as:
type RESULTS struct {
XMLName xml.Name `xml:"RESULTS"`
Text string `xml:",chardata"`
NAME []string `xml:"NAME"`
}
Try on playground

Golang unable to map XML to Struct

I want to map XML data to Struct object. I have following code:
package main
import (
"encoding/xml"
"fmt"
)
func main() {
type FileDetails struct {
XMLName xml.Name `xml:"FileDetails"`
FileName string
FileSize string
}
type DataRequest struct {
XMLName xml.Name `xml:"Data"`
DataRequestList []FileDetails
}
type Request struct {
XMLName xml.Name `xml:"Request"`
DataReqObject DataRequest `xml:"Data"`
}
req := Request{}
data := `
<Request>
<Data>
<FileDetails>
<FileName>abc</FileSize>
<FileSize>10</FileSize>
</FileDetails>
<FileDetails>
<FileName>pqr</FileSize>
<FileSize>20</FileSize>
</FileDetails>
</Data>
</Request>
`
err := xml.Unmarshal([]byte(data), &req)
if err != nil {
fmt.Printf("error: %v", err)
return
}
fmt.Printf("XMLName: %#v\n", req.XMLName)
fmt.Printf("XMLName: %v\n", req.DataReqObject)
fmt.Printf("XMLName: %v\n", req.DataReqObject.DataRequestList[0])
}
This can also be accessed here:
https://play.golang.org/p/VAMM9M2CejH
I'm getting following output with the above code:
XMLName: xml.Name{Space:"", Local:"Request"}
Data: {{ Data} []}
panic: runtime error: index out of range
Do the structs need to have diffferent structure for my data? Why is this mapping failing?
Three problems with your snippet:
#1
The tag xml:"FileDetails" is missing on DataRequestList
#2
The FileDetails struct does not match your xml in the provided playground link!
#3
<FileName> tag is closed with </FileSize> tag!
Go playground working example!

Go parsing XML file

i would like to parse a xml file and print values.
<alerts time="2017-09-14T15:46:00+02:00">
<alert type="A" identifier="123" declaredBy="Bob" />
<startedAt>20171010212210</startedAt>
<lastUpdate bySource="X">
<updatedAt>20171010213655</updatedAt>
<eventConfirmation>unconfirmed</eventConfirmation>
<additional>
<data name="numberOfOccurences">1</data>
</additional>
</lastUpdate>
<labels>
<label language="FR">attaque DNS</Label>
</labels>
</alert>
</alerts>
in this example i have just one alert but i can have more. I wrote 2 packages
package alerts
import "encoding/xml"
// Alert structure
type Alerts struct {
XMLName xml.Name `xml:"alerts"`
Time string `xml:"time,attr"`
Alert []alert `xml:"alert"`
}
type lastUpdate struct {
XMLName xml.Name `xml:"lastUpdate"`
BySource string `xml:"bySource,attr"`
UpdatedAt string `xml:"updatedAt"`
EventConfirmation string `xml:"eventConfirmation"`
Additional additional `xml:"additional"`
}
type additional struct {
XMLName xml.Name `xml:"additional"`
Data data `xml:"data"`
}
type data struct {
XMLName xml.Name `xml:"data"`
NumberOfOccurences int `xml:"numberOfOccurences,attr"`
}
type labels struct {
XMLName xml.Name `xml:"labels"`
Label []label `xml:"label"`
}
type label struct {
XMLName xml.Name `xml:"label"`
Label string `xml:"label"`
LabelLanguage string `xml:"language,attr"`
}
type alert struct {
XMLName xml.Name `xml:"alert"`
Type string `xml:"type,attr"`
Identifier string `xml:"identifier,attr"`
DeclaredBy string `xml:"declaredBy,attr"`
StartedAt string `xml:"startedAt"`
Lastupdate lastUpdate `xml:"lastupdate"`
Labels labels `xml:"label"`
}
my second package
package main
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"github.com/user/xmlparser/alert"
"github.com/golang/glog"
)
func main() {
//open XML file given in argument
xmlfile, err := os.Open(os.Args[1])
if err != nil {
glog.Fatalf("opening file %s - error : %s", os.Args[1], err)
}
defer xmlfile.Close()
// read our opened xmlFile as a byte array.
byteValue, _ := ioutil.ReadAll(xmlfile)
// we initialize our alerts array
var al alerts.Alerts
// we unmarshal our byteArray which contains our
// xmlFiles content into 'al' which we defined above
xml.Unmarshal(byteValue, &al)
for i := 0; i < len(al.Alert); i++ {
fmt.Printf("Alert time : %s\n\n", al.Time)
fmt.Println("Type: " + al.Alert[i].Type)
fmt.Println("Identifier: " + al.Alert[i].Identifier)
fmt.Println("declaredBy: " + al.Alert[i].DeclaredBy)
fmt.Printf("startAt: %s\n", al.Alert[i].StartedAt)
}
}
I can print Time, Type, Identifier and DeclaredBy but the variable StartedAt is empty. There is something that i don't understand. I think that my structure is not correctly defined.
If someone can help me ... Thanks !!!
It looks like you need to define StartedAt as more than just a string. Take a look at this question and example. You need to define StartedAt as it's own struct so you can access it's innerxml string.
type StartedAt struct {
Raw string `xml:",innerxml"`
}
type alert struct {
XMLName xml.Name `xml:"alert"`
Type string `xml:"type,attr"`
Identifier string `xml:"identifier,attr"`
DeclaredBy string `xml:"declaredBy,attr"`
StartedAt StartedAt `xml:"startedAt"`
}

How to marshal xml in Go but ignore empty fields

If I have a struct which I want to be able to Marhsal/Unmarshal things in and out of xml with (using encoding/xml) - how can I not print attributes which are empty?
package main
import (
"encoding/xml"
"fmt"
)
type MyThing struct {
XMLName xml.Name `xml:"body"`
Name string `xml:"name,attr"`
Street string `xml:"street,attr"`
}
func main() {
var thing *MyThing = &MyThing{Name: "Canister"}
result, _ := xml.Marshal(thing)
fmt.Println(string(result))
}
For example see http://play.golang.org/p/K9zFsuL1Cw
In the above playground I'd not want to write out my empty street attribute; how could I do that?
Use omitempty flag on street field.
From Go XML package:
a field with a tag including the "omitempty" option is omitted
if the field value is empty. The empty values are false, 0, any
nil pointer or interface value, and any array, slice, map, or
string of length zero.
In case of your example:
package main
import (
"encoding/xml"
"fmt"
)
type MyThing struct {
XMLName xml.Name `xml:"body"`
Name string `xml:"name,attr"`
Street string `xml:"street,attr,omitempty"`
}
func main() {
var thing *MyThing = &MyThing{Name: "Canister"}
result, _ := xml.Marshal(thing)
fmt.Println(string(result))
}
Playground

Missing Xml Tag in Golang

I am new to golang I am trying to make a xml in which i am using nested tags
For that my code is
type MyXml struct {
XMLName xml.Name `xml:"myXml"`
Id int `xml:"id,attr"`
NewXml
}
type NewXml struct {
XMLName xml.Name `xml:"newXml"`
OneMoreXml
}
type OneMoreXml struct {
Msg interface{} `xml:"oneMore"`
}
type Child struct {
Param1 string `xml:"Param1"`
}
func main() {
baseXml := &Child{Param1: "Param1"}
retXml := GetXml(baseXml)
fmt.Println("my xml is", retXml)
}
func MarshallXml(reqString interface{}) (newXml string) {
xmlBody, err := xml.Marshal(reqString)
if err != nil {
fmt.Printf("error: %v\n", err)
}
newXml = string(xmlBody)
//fmt.Println(newXml)
return
}
func GetXml(baseXml interface{}) (finalXml string) {
startXml := new(MyXml)
startXml.Id = 1
startXml.Msg = baseXml
finalXml = MarshallXml(startXml)
return
}
but in my output xml Tag newXml is missing. I have tried it in various ways but some tag is always missing. I guess i am not understanding struct tag properly. So What i am doing wrong in above code and which basic concept of golang struct i am missing
I had a look at the xml package doc and they say that "an anonymous struct field is handled as if the fields of its value were part of the outer struct". In your case, all fields thus get serialized as if they were part of MyXml.
NewXml does not have any field (you just give it a name but there is nothing else) so nothing gets serialized for it.
If you add a new field to it, you can see that it is serialized.
type NewXml struct {
XMLName xml.Name `xml:"newXml"`
Test int
OneMoreXml
}
http://play.golang.org/p/vibSeQHTCr

Resources