Is there any way to generate a OpenAPI specification document for XML using golang structure? My structures are annotated with encoding/xml annotations like this:
type Error struct {
Text string `xml:",chardata"`
Type string `xml:"Type,attr"`
Code string `xml:"Code,attr"`
ShortText string `xml:"ShortText,attr"`
}
I would like to generate a OpenAPI model automatically, which respect these annotations
You can use the reflect package from the standard library to inspect the struct and its tags and you can use the gopkg.in/yaml.v3 package to generate the Open API format. You just need to write the logic that translates your type into another, one that matches the structure of the Open API document.
Here's an example of how you could approach this, note that it is incomplete and should therefore not be used as is:
// declare a structure that matches the OpenAPI's yaml document
type DataType struct {
Type string `yaml:"type,omitempty"`
Props map[string]*DataType `yaml:"properties,omitempty"`
XML *XML `yaml:"xml,omitempty"`
}
type XML struct {
Name string `yaml:"name,omitempty"`
Attr bool `yaml:"attribute,omitempty"`
}
// write a marshal func that converts a given type to the structure declared above
// - this example converts only plain structs
func marshalOpenAPI(v interface{}) (interface{}, error) {
rt := reflect.TypeOf(v)
if rt.Kind() == reflect.Struct {
obj := DataType{Type: "object"}
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
tag := strings.Split(f.Tag.Get("xml"), ",")
if len(tag) == 0 || len(tag[0]) == 0 { // no name?
continue
}
if obj.Props == nil {
obj.Props = make(map[string]*DataType)
}
name := tag[0]
obj.Props[name] = &DataType{
Type: goTypeToOpenAPIType(f.Type),
}
if len(tag) > 1 && tag[1] == "attr" {
obj.Props[name].XML = &XML{Attr: true}
}
}
return obj, nil
}
return nil, fmt.Errorf("unsupported type")
}
// have all your types implement the yaml.Marshaler interface by
// delegating to the marshal func implemented above
func (e Error) MarshalYAML() (interface{}, error) {
return marshalOpenAPI(e)
}
// marshal the types
yaml.Marshal(map[string]map[string]interface{}{
"schemas": {
"error": Error{},
// ...
},
})
Try it on playground.
Related
I want to unmarshall the below xml payload to struct
<linearPackagePublish>
<linearPackage>
<name>ABC</name>
<packagedServiceReference>
<availabilityWindowEnd>2329-12-31 23:59:59</availabilityWindowEnd>
<availabilityWindowStart>2007-11-14 11:40:00</availabilityWindowStart>
<packagedServiceId>1111111111</packagedServiceId>
</packagedServiceReference>
<partnerPackageId>XXXXXXX</partnerPackageId>
</linearPackage>
<partnerId>XXXXXX</partnerId>
<wantLinearPublishResult>true</wantLinearPublishResult>
</linearPackagePublish>
I want to add transactionId just before tag like this:
<linearPackagePublish>
<linearPackage>
<name>ABC</name>
<packagedServiceReference>
<availabilityWindowEnd>2329-12-31 23:59:59</availabilityWindowEnd>
<availabilityWindowStart>2007-11-14 11:40:00</availabilityWindowStart>
<packagedServiceId>1111111111</packagedServiceId>
</packagedServiceReference>
<partnerPackageId>XXXXXXX</partnerPackageId>
</linearPackage>
<partnerId>XXXXXX</partnerId>
<transactionId>111111111111</transactionId>
<wantLinearPublishResult>true</wantLinearPublishResult>
</linearPackagePublish>
For this purpose I was trying to unmarshall the above xml to below struct:
type linearPackagePublish struct {
LinearPackage string `xml:"linearPackage"`
MsoPartnerID string `xml:"partnerId"`
TransactionID string `xml:"transactionId,omitempty"`
WantLinearPublishResult bool `xml:"wantLinearPublishResult,omitempty"`
}
But the thing is I don’t want to unmarshall the linearPackage element that is why I put LinearPackage as string in struct
I can also do it via regex but it will be more error prone if something change in the xml.
Is there any way that we can make some nested xml element as a string??
Edited to provide more detail. Including a working demo on Go Playground
Rather than trying to use a string you can define a custom type, with its own marshal and unmarshal functions defined. That way you can have direct control over that element.
You can see the custom datatype holds an array of xml.Tokens. This array is populated during unmarshalling and then written to the encoder during marshalling.
type skipUnmarshal struct {
data []xml.Token
}
func (s skipUnmarshal) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
for i := 0; i < len(s.data); i++ {
e.EncodeToken(s.data[i])
}
return nil
}
func (s *skipUnmarshal) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
t, err := d.Token()
if err != nil {
break // or alternatively return the error
}
s.data = append(s.data, t)
}
return nil
}
Short Description in Prose
I have a situation where I want to unmarshal JSON data into an array of structs (either Foo or Bar and many more) that all implement a common interface MyInterface. Also all of the eligble struct types that implement the interface have a common field which I named Discrimininator in the example below.
The Discriminator¹ allows to bi-uniquely find the correct struct type for each value of Discriminator.
The Problem and Error Message
But during unmarshling the code does not "know" which is the correct "target" type. The unmarshaling fails.
cannot unmarshal object into Go value of type main.MyInterface
MWE in
https://play.golang.org/p/Dw1hSgUezLH
package main
import (
"encoding/json"
"fmt"
)
type MyInterface interface {
// some other business logic methods go here!
GetDiscriminator() string // GetDiscriminator returns something like a key that is unique per struct type implementing the interface
}
type BaseStruct struct {
Discriminator string // will always be "Foo" for all Foos, will always be "Bar" for all Bars
}
type Foo struct {
BaseStruct
// actual fields of the struct don't matter. it's just important that they're different from Bar
FooField string
}
func (foo *Foo) GetDiscriminator() string {
return foo.Discriminator
}
type Bar struct {
BaseStruct
// actual fields of the struct don't matter. it's just important that they're different from Foo
BarField int
}
func (bar *Bar) GetDiscriminator() string {
return bar.Discriminator
}
// Foo and Bar both implement the interface.
// Foo and Bars are always distinguishible if we check the value of Discriminator
func main() {
list := []MyInterface{
&Bar{
BaseStruct: BaseStruct{Discriminator: "Bar"},
BarField: 42,
},
&Foo{
BaseStruct: BaseStruct{Discriminator: "Foo"},
FooField: "hello",
},
}
jsonBytes, _ := json.Marshal(list)
jsonString := string(jsonBytes)
fmt.Println(jsonString)
// [{"Discriminator":"Bar","BarField":42},{"Discriminator":"Foo","FooField":"hello"}]
var unmarshaledList []MyInterface
err := json.Unmarshal(jsonBytes, &unmarshaledList)
if err != nil {
// Unmarshaling failed: json: cannot unmarshal object into Go value of type main.MyInterface
fmt.Printf("Unmarshaling failed: %v", err)
}
}
In other languages
TypeNameHandling as known from .NET
In Newtonsoft, a popular .NET JSON Framework, this is solved by a something called "TypeNameHandling" or can be solved with a custom JsonConverter . The framework will add something like a magic "$type" key on root level to the serialized/marshaled JSON which is then used to determine the original type on deserialization/unmarshaling.
Polymorphism in ORMs
¹A similar situation occurs under the term "polymorphism" in ORMs when instances of multiple types having the same base are saved in the same table. One typically introduces a discriminator column, hence the name in above example.
You can implement a custom json.Unmarshaler. For that you'll need to use a named slice type instead of the unnamed []MyInterface.
Within the custom unmarshaler implementation you can unmarshal the JSON array into a slice where each element of the slice is a json.RawMessage representing the corresponding JSON object. After that you can iterate over the slice of raw messages. In the loop unmarshal from each raw message only the Discriminator field, then use the Discriminator field's value to determine what the correct type is into which the full raw message can be unmarshaled, finally unmarshal the full message and add the result to the receiver.
type MyInterfaceSlice []MyInterface
func (s *MyInterfaceSlice) UnmarshalJSON(data []byte) error {
array := []json.RawMessage{}
if err := json.Unmarshal(data, &array); err != nil {
return err
}
*s = make(MyInterfaceSlice, len(array))
for i := range array {
base := BaseStruct{}
data := []byte(array[i])
if err := json.Unmarshal(data, &base); err != nil {
return err
}
var elem MyInterface
switch base.Discriminator {
case "Foo":
elem = new(Foo)
case "Bar":
elem = new(Bar)
}
if elem == nil {
panic("whoops")
}
if err := json.Unmarshal(data, elem); err != nil {
return err
}
(*s)[i] = elem
}
return nil
}
https://play.golang.org/p/mXiZrF392aV
I want to translate some fields of my Rest response from English to Hindi language. I have few translator files where I have mapping of words from English to Hindi. The name of the file I want to provide via field tags.
So my struct will look something like this
type myResponse struct {
City string `translatorFile:"CityEToH"`
State string `translatorFile:"StateEToH"`
StationCode []string `translatorFile:"StationCodeEToH"`
InsideStruct insideStruct
}
type insideStruct struct {
trainName string `translatorFile:"TrainEToH"`
StartingPoint string `translatorFile:"StationCodeEToH"`
FinishPoint string `translatorFile:"StationCodeEToH"`
}
I want to write a common translator method that will take interface{} as the input parameter and will return an interface (after converting the input) as output. I have just started to learn Go and I am stuck with the implementation. I am not able to create a map kind of structure that will map the fieldName to the corresponding translator file Name.
I have tried reflect.typeOf(input), but with this I am not able to get the tags of insideStruct. This is just an example structure of payload, I could have 4-5 inherited level of struct as well.
Is there a way to get the fieldName, tags and fieldValue together. Or is there any other better way to implement this ?
Here's a function that walks through values and calls a function for each string with the associated struct tag:
func walkStrings(v reflect.Value, tag reflect.StructTag, fn func(reflect.Value, reflect.StructTag)) {
v = reflect.Indirect(v)
switch v.Kind() {
case reflect.Struct:
t := v.Type()
for i := 0; i < t.NumField(); i++ {
walkStrings(v.Field(i), t.Field(i).Tag, fn)
}
case reflect.Slice, reflect.Array:
if v.Type().Elem().Kind() == reflect.String {
for i := 0; i < v.Len(); i++ {
walkStrings(v.Index(i), tag, fn)
}
}
case reflect.String:
fn(v, tag)
}
}
The function fn can use Value.String to get the value as a string and Value.SetString to change the value. Use StructTag.Get to get the tag. An example function is:
func translate(v reflect.Value, tag reflect.StructTag) {
if !v.CanSet() {
// unexported fields cannot be set
return
}
file := tag.Get("translatorFile")
if file == "" {
return
}
v.SetString(translatStringWithFile(v.String(), file)
}
Call walkStrings with a reflect.Value of a struct pointer and the empty string as the tag:
v := myResponse{
City: "mycity",
StationCode: []string{"code1", "code2"},
InsideStruct: insideStruct{"trainname", "start", "finish"},
}
walkStrings(reflect.ValueOf(&v), "", translate)
Run it on the playground.
I have a function for decode http response json to a struct.And I have two types of structs needed to pass to this function, and have the type of struct as return value to get the decoded json.
My function now can deal with ONE type, need help to make it can handle different type of struct, and return the struct.
// Response json
type responseResult struct {
result string
}
type loginResult struct {
responseResult
token string
}
func responseBodyDecoder(resp http.Response,response *responseResult) {
// get result form Response
decoder := json.NewDecoder(resp.Body)
decode_err := decoder.Decode(&response)
if decode_err != nil {
panic(decode_err)
}
}
you can use type interface{}:
func responseBodyDecoder(resp http.Response,response interface{}) {
// get result form Response
decoder := json.NewDecoder(resp.Body)
decode_err := decoder.Decode(response)
if decode_err != nil {
panic(decode_err)
}
}
now, you can:
ret:=loginResult{}
responseBodyDecoder(resp, &ret)
ret2:=responseResult{}
responseBodyDecoder(resp, &ret2)
but careful, both struct responseResult and responseResult don't export any element.
you must modify the definition:
type responseResult struct {
Result string
}
I have the following:
https://play.golang.org/p/ADX6H-bh0CU
package main
import (
"encoding/xml"
"fmt"
)
type xmlMap map[string]string
type xmlMapEntry struct {
XMLName xml.Name
Value string `xml:",chardata"`
}
func (m xmlMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if len(m) == 0 {
return nil
}
err := e.EncodeToken(start)
if err != nil {
return err
}
for k, v := range m {
e.Encode(xmlMapEntry{XMLName: xml.Name{Local: k}, Value: v})
}
return e.EncodeToken(start.End())
}
func main() {
type Family struct {
Siblings map[string]string
}
type MyFamily struct {
Siblings xmlMap `json:"siblings" xml:"siblings"`
}
// In reality, "f" comes from a function in an outside package...e.g. "f := somepackage.Function()"
f := &Family{
Siblings: map[string]string{"bob": "brother", "mary": "sister"},
}
var m = &MyFamily{}
*m = MyFamily(*f)
x, err := xml.Marshal(m)
if err != nil {
panic(err)
}
fmt.Println(string(x))
}
The Family struct comes from an outside package. I created MyFamily struct in order to add json and xml tags. If I try to xml encode MyFamily then I get an error:
xml: unsupported type: map[string]string
This is easily resolved by implementing the custom XML marshaller that I have done.
This creates a separate problem. I have tried to simplify the example but "f" comes from a function in an outside package...e.g. "f := somepackage.Function()". When I try to Alias f to m I get the error:
cannot convert *f (type Family) to type MyFamily
This happens because Family has a map[string]string but MyFamily has xmlMap. Even though they are the same underlying type I get that error.
The question is, how do I implement my custom struct tags AND be able to XML encode a map?
EDIT:
This is a simple example and can just be resolved by
var m = &MyFamily{Siblings: f.Siblings}
Since there are MANY fields in both Family and MyFamily this is just inefficient and just wanted to know if there's a better way.