Golang yaml generation double entries - parsing

I want to generate the following in yaml:
- bar: hello
- bar: another
pint: guiness
- bar: second
pint: ""
in Golang, however making the following example, I get the output below:
- bar:
- bar: hello
- bar: another
pint: guiness
- bar:
- bar: second
pint: ""
It seems the YAML Golang parser puts the names of the structs in the YAML it generates like - bar: and then the member array under it. I don't want that, as it will break other things.
package main
import (
"fmt"
"gopkg.in/yaml.v2"
"log"
)
type bar struct {
Bar string
}
type foo struct {
Bars []bar `yaml:"bar"`
Pint string `yaml:"pint"`
}
func main() {
f := make([]foo, 2)
f[0].Bars = make([]bar, 2)
f[0].Bars[0].Bar = "hello"
f[0].Bars[1].Bar = "another"
f[0].Pint = "guiness"
f[1].Bars = make([]bar, 1)
f[1].Bars[0].Bar = "second"
y, err := yaml.Marshal(f)
if err != nil {
log.Fatalf("Marshal: %v", err)
}
fmt.Println(string(y))
}
I wondered if there is a way to get it to generate it like the first example?
Even if it means I have to use another YAML library.

Take a look at this example:
package main
import (
"fmt"
"log"
yaml "gopkg.in/yaml.v2"
)
type T struct {
Bar string `yaml:"bar,omitempty"`
Pint string `yaml:"pint,omitempty"`
}
func main() {
var t = make([]T, 3)
t[0].Bar = "hello"
t[1].Bar = "another"
t[1].Pint = "guiness"
t[2].Bar = "second"
y, err := yaml.Marshal(t)
if err != nil {
log.Fatalf("Marshal: %v", err)
}
fmt.Println(string(y))
}
Output:
- bar: hello
- bar: another
pint: guiness
- bar: second
If you want to keep the empty string like in your desired output then you can do this
package main
import (
"fmt"
"log"
yaml "gopkg.in/yaml.v2"
)
type S string
func (s *S) IsZero() bool {
return false
}
type T struct {
Bar string `yaml:"bar,omitempty"`
Pint *S `yaml:"pint,omitempty"`
}
func main() {
var t = make([]T, 3)
t[0].Bar = "hello"
t[1].Bar = "another"
p1 := S("guiness")
t[1].Pint = &p1
t[2].Bar = "second"
p2 := S("")
t[2].Pint = &p2
y, err := yaml.Marshal(t)
if err != nil {
log.Fatalf("Marshal: %v", err)
}
fmt.Println(string(y))
}
Output:
- bar: hello
- bar: another
pint: guiness
- bar: second
pint: ""
More info on yaml package: https://godoc.org/gopkg.in/yaml.v2

Related

Dynamically parse yaml field to one of a finite set of structs in Go

I have a yaml file, where one field could be represented by one of possible kinds of structs. To simplify the code and yaml files, let's say I have these yaml files:
kind: "foo"
spec:
fooVal: 4
kind: "bar"
spec:
barVal: 5
And these structs for parsing:
type Spec struct {
Kind string `yaml:"kind"`
Spec interface{} `yaml:"spec"`
}
type Foo struct {
FooVal int `yaml:"fooVal"`
}
type Bar struct {
BarVal int `yaml:"barVal"`
}
I know that I can use map[string]interface{} as a type of Spec field. But the real example is more complex, and involves more possible struct types, not only Foo and Bar, this is why I don't like to parse spec into the field.
I've found a workaround for this: unmarshal the yaml into intermediate struct, then check kind field, and marshal map[string]interface{} field into yaml back, and unmarshal it into concrete type:
var spec Spec
if err := yaml.Unmarshal([]byte(src), &spec); err != nil {
panic(err)
}
tmp, _ := yaml.Marshal(spec.Spec)
if spec.Kind == "foo" {
var foo Foo
yaml.Unmarshal(tmp, &foo)
fmt.Printf("foo value is %d\n", foo.FooVal)
}
if spec.Kind == "bar" {
tmp, _ := yaml.Marshal(spec.Spec)
var bar Bar
yaml.Unmarshal(tmp, &bar)
fmt.Printf("bar value is %d\n", bar.BarVal)
}
But it requires additional step and consumes more memory (real yaml file could be bigger than in examples). Does some more elegant way exist to unmarshal yaml dynamically into a finite set of structs?
Update: I'm using github.com/go-yaml/yaml v2.1.0 Yaml parser.
For use with yaml.v2 you can do the following:
type yamlNode struct {
unmarshal func(interface{}) error
}
func (n *yamlNode) UnmarshalYAML(unmarshal func(interface{}) error) error {
n.unmarshal = unmarshal
return nil
}
type Spec struct {
Kind string `yaml:"kind"`
Spec interface{} `yaml:"-"`
}
func (s *Spec) UnmarshalYAML(unmarshal func(interface{}) error) error {
type S Spec
type T struct {
S `yaml:",inline"`
Spec yamlNode `yaml:"spec"`
}
obj := &T{}
if err := unmarshal(obj); err != nil {
return err
}
*s = Spec(obj.S)
switch s.Kind {
case "foo":
s.Spec = new(Foo)
case "bar":
s.Spec = new(Bar)
default:
panic("kind unknown")
}
return obj.Spec.unmarshal(s.Spec)
}
https://play.golang.org/p/Ov0cOaedb-x
For use with yaml.v3 you can do the following:
type Spec struct {
Kind string `yaml:"kind"`
Spec interface{} `yaml:"-"`
}
func (s *Spec) UnmarshalYAML(n *yaml.Node) error {
type S Spec
type T struct {
*S `yaml:",inline"`
Spec yaml.Node `yaml:"spec"`
}
obj := &T{S: (*S)(s)}
if err := n.Decode(obj); err != nil {
return err
}
switch s.Kind {
case "foo":
s.Spec = new(Foo)
case "bar":
s.Spec = new(Bar)
default:
panic("kind unknown")
}
return obj.Spec.Decode(s.Spec)
}
https://play.golang.org/p/ryEuHyU-M2Z
You can do this by implementing a custom UnmarshalYAML func. However, with the v2 version of the API, you would basically do the same thing as you do now and just encapsulate it a bit better.
If you switch to using the v3 API however, you get a better UnmarshalYAML that actually lets you work on the parsed YAML node before it is processed into a native Go type. Here's how that looks:
package main
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
)
type Spec struct {
Kind string `yaml:"kind"`
Spec interface{} `yaml:"spec"`
}
type Foo struct {
FooVal int `yaml:"fooVal"`
}
type Bar struct {
BarVal int `yaml:"barVal"`
}
func (s *Spec) UnmarshalYAML(value *yaml.Node) error {
s.Kind = ""
for i := 0; i < len(value.Content)/2; i += 2 {
if value.Content[i].Kind == yaml.ScalarNode &&
value.Content[i].Value == "kind" {
if value.Content[i+1].Kind != yaml.ScalarNode {
return errors.New("kind is not a scalar")
}
s.Kind = value.Content[i+1].Value
break
}
}
if s.Kind == "" {
return errors.New("missing field `kind`")
}
switch s.Kind {
case "foo":
var foo Foo
if err := value.Decode(&foo); err != nil {
return err
}
s.Spec = foo
case "bar":
var bar Bar
if err := value.Decode(&bar); err != nil {
return err
}
s.Spec = bar
default:
return errors.New("unknown kind: " + s.Kind)
}
return nil
}
var input1 = []byte(`
kind: "foo"
spec:
fooVal: 4
`)
var input2 = []byte(`
kind: "bar"
spec:
barVal: 5
`)
func main() {
var s1, s2 Spec
if err := yaml.Unmarshal(input1, &s1); err != nil {
panic(err)
}
fmt.Printf("Type of spec from input1: %T\n", s1.Spec)
if err := yaml.Unmarshal(input2, &s2); err != nil {
panic(err)
}
fmt.Printf("Type of spec from input2: %T\n", s2.Spec)
}
I suggest looking into the possibility of using YAML tags instead of your current structure to model this in your YAML; tags have been designed exactly for this purpose. Instead of the current YAML
kind: "foo"
spec:
fooVal: 4
you could write
--- !foo
fooVal: 4
Now you don't need the describing structure with kind and spec anymore. Loading this would look a bit different as you'd need a wrapping root type you can define UnmarshalYAML on, but it may be feasible if this is just a part of a larger structure. You can access the tag !foo in the yaml.Node's Tag field.

Some url for parser

I have an url array which I want to parse, how can I get it?
What I want is to see all the url in the same recyclerView.
doAsync {
val array = arrayOf("https://rafelcf.000webhostapp.com/rafel_cf/1.php",
"https://rafelcf.000webhostapp.com/rafel_cf/2.php")
array.forEach
val url = (it)
val stringRequest = StringRequest(Request.Method.GET, url, Response.Listener { response ->
val builder = GsonBuilder()
val mGson = builder.create()
uiThread {
val items: List<ModelJor>
items = (Arrays.asList(*mGson.fromJson(response, Array::class.java)))
val filtro = items.filter { it.estadoPartido == "Pendiente" && it.fecha != "" }
recyclerView!!.layoutManager = GridLayoutManager(activity!!, 1)
val adapter = AdapNJ(activity!!, filtro)
recyclerView!!.adapter = adapter
adapter.notifyDataSetChanged();
}
}, Response.ErrorListener { error -> Log.d(TAG, "Error " + error.message) })
queue.add(stringRequest)
}
See Step by step below.
You can use the following statement to get the response of each url.
val arr = arrayOf("https://rafelcf.000webhostapp.com/rafel_cf/1.php",
"https://rafelcf.000webhostapp.com/rafel_cf/2.php")
arr.forEach {
val jsonText = URL(it).readText()
// parse jsonText to objects
println(jsonText)
}
To parse json text, you can follow the instructions here.
Edit
To implement network connection in android,
you need to do the task in the separate thread, not in UI Thread.
Using Kotlin anko, you can easily achieve this using doAsync.
Just wrap the code to be run in doAsync clause
and wrap the resulting code in uiThread clause.
doAsync {
val arr = arrayOf(...)
arr.forEach {
...
val result = // parsed result
uiThread {
doSomethingWithResult(result)
}
}
}
Step by Step
Okay I'll give you step by step instructions so you can follow along with your project.
1. Add internet permission
We will be using internet connection, so we have to tell that we need permission to use the internet.
Open app/manifests/AndroidManifest.xml.
Add the following tag as a child of root <manifest> tag.
<uses-permission android:name="android.permission.INTERNET"/>
2. Add anko library
We will be using anko library so we can handle async tasks easily without boilerplates.
Open Gradle Scripts/build.gradle (Module: app)
Add following line inside dependencies.
implementation "org.jetbrains.anko:anko:0.10.8"
Click Sync Now.
3. doAsync{} for background tasks
To request in background, we are using doAsync by anko.
Place doAsync clause in where you have to make a request.
In my case, I'll be sending request when user clicks a button.
btnLoad.setOnClickListener {
doAsync {
}
}
This will cause a compile error.
To solve this, simply import doAsync.
4. Loop through urls and fetch response
Now we are in background, we can make requests!
Inside doAsync, request and fetch the response.
val arr = arrayOf("https://rafelcf.000webhostapp.com/rafel_cf/1.php",
"https://rafelcf.000webhostapp.com/rafel_cf/2.php")
arr.forEach {
// request and fetch response
val jsonText = URL(it).readText()
}
Now we have response text in jsonText.
5. Parse response
I'll just use JSONArray and JSONObject to parse jsonText.
But it is your choice which method you will use.
You can use Gson as you described in your question.
As we now that response is in array, we can do as follows:
// we know that result json is an array
val jsonArray = JSONArray(jsonText)
for(i in 0 until jsonArray.length()) {
// get each elements
val jsonObject = jsonArray[i] as JSONObject
// get data of each elements
val idLocal = jsonObject.getString("idLocal")
val idClubLocal = jsonObject.getString("idClubLocal")
val nomLocal = jsonObject.getString("nomLocal")
}
6. Do something with parsed results
Now that we have parsed data, only thing left is to do something with it.
I'll simply log the data to the logcat.
I defined log() to simplify logging codes.
fun log(message: String) = Log.d("MainActivity", message)
Now I logged the data I fetched.
log("[item $i]")
log("idLocal: $idLocal")
log("idClubLocal: $idClubLocal")
log("nomLocal: $nomLocal")
From this point, when you run this code you'll be able to see the result like this in logcat.
MainActivity: [item 0]
MainActivity: idLocal: 0201010551
MainActivity: idClubLocal: 0201010
MainActivity: nomLocal: AAAAA
MainActivity: [item 1]
MainActivity: idLocal: 0201045201
MainActivity: idClubLocal: 0201045
MainActivity: nomLocal: BBBBB
MainActivity: [item 0]
MainActivity: idLocal: 0201010551
MainActivity: idClubLocal: 0201010
MainActivity: nomLocal: CCCCC
MainActivity: [item 1]
MainActivity: idLocal: 0201045201
MainActivity: idClubLocal: 0201045
MainActivity: nomLocal: DDDDD
As you see, these are the data that your urls provide.
7. Let something run in uiThread
Notice that all our code is running in async.
Sometimes, we need to do something with the result in ui thread, such as
Update TextView as request
Add new item to RecyclerView
For now, I will stick on logging data instead of updating uis.
To run code in ui thread, simply wrap the code with uiThread clause.
You might also have to import uiThread.
uiThread {
log("[item $i]")
log("idLocal: $idLocal")
log("idClubLocal: $idClubLocal")
log("nomLocal: $nomLocal")
}
The code works exactly same except the logging now works in ui thread.
FULL CODE
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnLoad.setOnClickListener {
doAsync {
val arr = arrayOf("https://rafelcf.000webhostapp.com/rafel_cf/1.php",
"https://rafelcf.000webhostapp.com/rafel_cf/2.php")
arr.forEach {
val jsonText = URL(it).readText()
// we know that result json is an array
val jsonArray = JSONArray(jsonText)
for(i in 0 until jsonArray.length()) {
// get each elements
val jsonObject = jsonArray[i] as JSONObject
// get data of each elements
val idLocal = jsonObject.getString("idLocal")
val idClubLocal = jsonObject.getString("idClubLocal")
val nomLocal = jsonObject.getString("nomLocal")
uiThread {
log("[item $i]")
log("idLocal: $idLocal")
log("idClubLocal: $idClubLocal")
log("nomLocal: $nomLocal")
}
}
}
}
}
}
fun log(message: String) = Log.d("MainActivity", message)
}
The Solution:
fun getPendientes() {
doAsync {
for(num in 1..30) {
val arr = arrayOf(
"http://www.url$num")
arr.forEach {
val jsonText = URL(it).readText()
val jsonArray = JSONArray(jsonText)
for (i in 0 until jsonArray.length()) {
val pendientes = jsonArray.optJSONObject(i)
val nomLocal = pendientes.getString("nomLocal")
val resulLocal = pendientes.getString("resulLocal")
val escudoLocal = pendientes.getString("escudoLocal")
val nomVisitante = pendientes.getString("nomVisitante")
val resulVisitante = pendientes.getString("resulVisitante")
val escudoVisitante = pendientes.getString("escudoVisitante")
val fecha = pendientes.getString("fecha")
val hora = pendientes.getString("hora")
val estadoPartido = pendientes.getString("estadoPartido")
val abreviaturaEstado = pendientes.getString("abreviaturaEstado")
modelPendientes.add(ModelPendientes(nomLocal, resulLocal, escudoLocal,
nomVisitante, resulVisitante, escudoVisitante, fecha, hora, estadoPartido,abreviaturaEstado))
val filtro = modelPendientes
.filter {it ->
it.abreviaturaEstado == "P" ||
it.abreviaturaEstado == "A" ||
it.abreviaturaEstado == "S" &&
it.fecha != ""
}
uiThread {
/*val filtro = modelPendientes.filter { it.abreviaturaEstado == "A" ||
it.abreviaturaEstado == "S" || it.abreviaturaEstado == "P" && it.fecha != ""*/
//}
recyclerView!!.layoutManager = GridLayoutManager(activity!!, 1)
adapter = PendientesAdapter(filtro, activity!!)
recyclerView!!.adapter = adapter
}
}
}
}
}
}

How to flatten message with mutable structure into protobuf?

My proto buf format is this:
package main;
message Test {
optional string id = 1;
optional string name = 2;
optional string age = 3;
}
Then I populate the protobuf files from the input in golang using the following code. str is already parsed.
test = &Test{
id: proto.String(str[0]),
name: proto.String(str[1]),
age: proto.String(str[2]),
},
One condition I have to handle is that one or more optional fields in the Test structure could be absent randomly and I do not know that in advance. How do I handle that in golang?
To give more context, the real data can look like these in the file:
id=1, name=peter, age=24
id=2, age=30
name=mary, age=31
id=100
name=bob
age=11
You could use a regular expression to change your input strings into valid JSON, the use the encoding/json package to parse it. This has the advantage of letting the json parser take care of everything for you. Here is the regex for your particular case.
Basically, the regex looks for field=value and replaces with "field" : "value" and wraps it in {} to create valid JSON. The commas are left as is.
https://play.golang.org/p/_EEdTB6sve
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"regexp"
)
var ins = []string{
`id=1, name=peter, age=24`,
`id=2, age=30`,
`name=mary, age=31`,
`id=100`,
`name=bob`,
`age=11`,
}
var ParseError = errors.New("bad parser input")
var Regex *regexp.Regexp
type Test struct {
ID string
Name string
Age string
}
func (t *Test) String() string {
return fmt.Sprintf("ID: %s, Name: %s, Age: %s", t.ID, t.Name, t.Age)
}
func main() {
var err error
Regex, err = regexp.Compile(`([^,\s]*)=([^,\s]*)`)
if err != nil {
log.Panic(err)
}
for _, v := range ins {
test, err := ParseLine(v)
if err != nil {
log.Panic(err)
}
fmt.Println(test)
}
}
func ParseLine(inp string) (*Test, error) {
JSON := fmt.Sprintf("{%s}", Regex.ReplaceAllString(inp, `"$1" : "$2"`))
test := &Test{}
err := json.Unmarshal([]byte(JSON), test)
if err != nil {
return nil, err
}
return test, nil
}
Here is what I believe to be a minimum working case for what you are after, though I am not familiar enough with protocol buffers to get the strings to print right... or even verify if they are correct. Note that this doesn't run in the playground.
package main
import (
"errors"
"fmt"
"log"
"regexp"
"github.com/golang/protobuf/jsonpb"
_ "github.com/golang/protobuf/proto"
)
var ins = []string{
`id=1, name=peter, age=24`,
`id=2, age=30`,
`name=mary, age=31`,
`id=100`,
`name=bob`,
`age=11`,
}
var ParseError = errors.New("bad parser input")
var Regex *regexp.Regexp
type Test struct {
ID *string `protobuf:"bytes,1,opt,name=id,json=id" json:"id,omitempty"`
Name *string `protobuf:"bytes,2,opt,name=name,json=name" json:"name,omitempty"`
Age *string `protobuf:"bytes,3,opt,name=age,json=age" json:"age,omitempty"`
}
func (t *Test) Reset() {
*t = Test{}
}
func (*Test) ProtoMessage() {}
func (*Test) Descriptor() ([]byte, []int) {return []byte{}, []int{0}}
func (t *Test) String() string {
return fmt.Sprintf("ID: %v, Name: %v, Age: %v", t.ID, t.Name, t.Age)
}
func main() {
var err error
Regex, err = regexp.Compile(`([^,\s]*)=([^,\s]*)`)
if err != nil {
log.Panic(err)
}
for _, v := range ins {
test, err := ParseLine(v)
if err != nil {
fmt.Println(err)
log.Panic(err)
}
fmt.Println(test)
}
}
func ParseLine(inp string) (*Test, error) {
JSON := fmt.Sprintf("{%s}", Regex.ReplaceAllString(inp, `"$1" : "$2"`))
test := &Test{}
err := jsonpb.UnmarshalString(JSON, test)
if err != nil {
return nil, err
}
return test, nil
}
Looks like you can write the parser for each line of your input something like the following.
NOTE: I didn't make the struct with proto values because as an external package, it can't be imported in the playground.
https://play.golang.org/p/hLZvbiMMlZ
package main
import (
"errors"
"fmt"
"strings"
)
var ins = []string{
`id=1, name=peter, age=24`,
`id=2, age=30`,
`name=mary, age=31`,
`id=100`,
`name=bob`,
`age=11`,
}
var ParseError = errors.New("bad parser input")
type Test struct {
ID string
Name string
Age string
}
func (t *Test) String() string {
return fmt.Sprintf("ID: %s, Name: %s, Age: %s", t.ID, t.Name, t.Age)
}
func main() {
for _, v := range ins {
t, err := ParseLine(v)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(t)
}
}
}
func ParseLine(inp string) (*Test, error) {
splt := strings.Split(inp, ",")
test := &Test{}
for _, f := range splt {
fieldVal := strings.Split(strings.TrimSpace(f), "=")
switch strings.TrimSpace(fieldVal[0]) {
case "id":
test.ID = strings.TrimSpace(fieldVal[1])
case "name":
test.Name = strings.TrimSpace(fieldVal[1])
case "age":
test.Age = strings.TrimSpace(fieldVal[1])
default:
return nil, ParseError
}
}
return test, nil
}

How to parse a method declaration?

I'm trying to parse a method declaration. Basically I need to get the syntax node of the receiver base type (type hello) and the return types (notype and error). The ast package seems straightforward but for some reason I don't get the data I need (i.e. the fields are reported nil).
The only useful data seems provided only in Object -> Decl field which is of type interface{} so I don't think I can serialize it.
Any help would be appreciated. Code below:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
// src is the input for which we want to inspect the AST.
src := `
package mypack
// type hello is a cool type
type hello string
// type notype is not that cool
type notype int
// func printme is like nothing else.
func (x *hello)printme(s string)(notype, error){
return 0, nil
}
`
// Create the AST by parsing src.
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "src.go", src, 0)
if err != nil {
panic(err)
}
// Inspect the AST and find our function
var mf ast.FuncDecl
ast.Inspect(f, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.FuncDecl:
mf = *x
}
return true
})
if mf.Recv != nil {
fmt.Printf("\n receivers:")
for _, v := range mf.Recv.List {
fmt.Printf(",tag %v", v.Tag)
for _, xv := range v.Names {
fmt.Printf("name %v, decl %v, data %v, type %v",
xv.Name, xv.Obj.Decl, xv.Obj.Data, xv.Obj.Type)
}
}
}
}
Playground
To get the type you need to look at the Type attribute which could be an ast.StarExpr or an ast.Ident.
Here take a look at this :
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
// src is the input for which we want to inspect the AST.
src := `
package mypack
// type hello is a cool type
type hello string
// type notype is not that cool
type notype int
// printme is like nothing else.
func (x *hello)printme(s string)(notype, error){
return 0, nil
}
`
// Create the AST by parsing src.
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "src.go", src, 0)
if err != nil {
panic(err)
}
// Inspect the AST and find our function
var mf ast.FuncDecl
ast.Inspect(f, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.FuncDecl:
mf = *x
}
return true
})
if mf.Recv != nil {
for _, v := range mf.Recv.List {
fmt.Print("recv type : ")
switch xv := v.Type.(type) {
case *ast.StarExpr:
if si, ok := xv.X.(*ast.Ident); ok {
fmt.Println(si.Name)
}
case *ast.Ident:
fmt.Println(xv.Name)
}
}
}
}

Unmarshalling XML with (xpath)conditions

I'm trying to unmarshall some XML which is structured like the following example:
<player>
<stat type="first_name">Somebody</stat>
<stat type="last_name">Something</stat>
<stat type="birthday">06-12-1987</stat>
</player>
It's dead easy to unmarshal this into a struct like
type Player struct {
Stats []Stat `xml:"stat"`
}
but I'm looking to find a way to unmarshal it into a struct that's more like
type Player struct {
FirstName string `xml:"stat[#Type='first_name']"`
LastName string `xml:"stat[#Type='last_name']"`
Birthday Time `xml:"stat[#Type='birthday']"`
}
is there any way to do this with the standard encoding/xml package?
If not, can you give me a hint how one would split down such a "problem" in go? (best practices on go software architecture for such a task, basically).
thank you!
The encoding/xml package doesn't implement xpath, but does have a simple set of selection methods it can use.
Here's an example of how you could unmarshal the XML you have using encoding/xml. Because the stats are all of the same type, with the same attributes, the easiest way to decode them will be into a slice of the same type. http://play.golang.org/p/My10GFiWDa
var doc = []byte(`<player>
<stat type="first_name">Somebody</stat>
<stat type="last_name">Something</stat>
<stat type="birthday">06-12-1987</stat>
</player>`)
type Player struct {
XMLName xml.Name `xml:"player"`
Stats []PlayerStat `xml:"stat"`
}
type PlayerStat struct {
Type string `xml:"type,attr"`
Value string `xml:",chardata"`
}
And if it's something you need to transform often, you could do the transformation by using an UnamrshalXML method: http://play.golang.org/p/htoOSa81Cn
type Player struct {
XMLName xml.Name `xml:"player"`
FirstName string
LastName string
Birthday string
}
func (p *Player) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
t, err := d.Token()
if err == io.EOF {
break
} else if err != nil {
return err
}
if se, ok := t.(xml.StartElement); ok {
t, err = d.Token()
if err != nil {
return err
}
var val string
if c, ok := t.(xml.CharData); ok {
val = string(c)
} else {
// not char data, skip for now
continue
}
// assuming we have exactly one Attr
switch se.Attr[0].Value {
case "first_name":
p.FirstName = val
case "last_name":
p.LastName = val
case "birthday":
p.Birthday = val
}
}
}
return nil
}

Resources