4

I want to parse some JSON but one key is either a string or an object.

Here is my current struct: https://github.com/PhillippOhlandt/pmtoapib/blob/master/CollectionItemRequest.go#L10

type CollectionItemRequest struct {
    Url         string          `json:"url"`
    Method      string          `json:"method"`
    Header      []RequestHeader `json:"header"`
    Body        RequestBody     `json:"body"`
    Description string          `json:"description"`
}

Here the "Url" attribute can not only a string but also an object.

I started to create an own struct for it that covers the object case.

type CollectionItemRequestUrl struct {
    Raw string `json:"raw"`
}

type CollectionItemRequest struct {
    Url         CollectionItemRequestUrl `json:"url"`
    Method      string                   `json:"method"`
    Header      []RequestHeader          `json:"header"`
    Body        RequestBody              `json:"body"`
    Description string                   `json:"description"`
}

But then the string version won't work anymore. Is there a way to have both cases working and getting the value via a getter, like request.Url.Get?

EDIT:

Here are the two versions of the JSON:

    "request": {
        "url": {
            "raw": "http://localhost:8081/users?per_page=5&page=2",
            "protocol": "http",
            "host": [
                "localhost"
            ],
            "port": "8081",
            "path": [
                "users"
            ],
            "query": [
                {
                    "key": "per_page",
                    "value": "5",
                    "equals": true,
                    "description": ""
                },
                {
                    "key": "page",
                    "value": "2",
                    "equals": true,
                    "description": ""
                }
            ],
            "variable": []
        },

And

"request": {
                "url": "http://localhost:8081/users/2",

Note: Only subsets, the whole JSON would be too long.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Phillipp
  • 1,425
  • 2
  • 12
  • 27
  • Show examples of the JSON. – Charlie Tumahai Jan 17 '18 at 16:21
  • Possible duplicates: https://stackoverflow.com/questions/18526046/mapping-strings-to-multiple-types-for-json-objects, https://stackoverflow.com/questions/44057958/go-unmarshalling-json-with-multiple-types, https://stackoverflow.com/questions/47057240/parsing-multiple-json-types-into-the-same-struct, https://stackoverflow.com/questions/40988252/golang-convert-json-with-variable-types-to-strings, https://stackoverflow.com/questions/11066946/partly-json-unmarshal-into-a-map-in-go – JimB Jan 17 '18 at 16:24
  • The "duplicates" handle much simpler cases where there are different object types or integer/string, etc. But not string/object in a big nested structure. – Phillipp Jan 17 '18 at 16:26
  • @Cerise Limón I added JSON examples – Phillipp Jan 17 '18 at 16:28
  • 2
    @Phillipp Some of the answers in the questions @JimB linked should work for you. Specifically, a struct with the `UnmarshalJSON` function should work just fine in this case. – Tulir Jan 17 '18 at 16:32

1 Answers1

5

Have a type that has a custom unmarshal method that will first unmarshal into an empty interface and then does a type switch on whether it got a string or a map[string]interface{} such as this:

type example struct {
    URL myURL `json:"url"`
}

type myURL struct {
    url string
}

func (u *myURL) MarshalJSON() ([]byte, error) {
    return json.Marshal(u.url)
}

func (u *myURL) UnmarshalJSON(data []byte) error {
    var raw interface{}
    json.Unmarshal(data, &raw)
    switch raw := raw.(type) {
    case string:
        *u = myURL{raw}
    case map[string]interface{}:
        *u = myURL{raw["raw"].(string)}
    }
    return nil
}

const myStringURL string = `{"url": "http://www.example.com/as-string"}`
const myNestedURL string = `{"url": {"raw": "http://www.example.com/as-nested"}}`

func main() {
    var stringOutput example
    json.Unmarshal([]byte(myStringURL), &stringOutput)
    fmt.Println(stringOutput)

    var nestedOutput example
    json.Unmarshal([]byte(myNestedURL), &nestedOutput)
    fmt.Println(nestedOutput)
}

Runnable in go playground here:

https://play.golang.org/p/I6KC4aXHpxm

Iain Duncan
  • 3,139
  • 2
  • 17
  • 28
  • Thanks, that works! I was already on the right path with the `UnmarshalJSON ` method but had some problems assigning the value. – Phillipp Jan 17 '18 at 16:56