简体   繁体   中英

How can I define a GoLang structure with varying field types

I'm relatively new to GoLang, and am having trouble with the strict typing system (I'm much more used to weakly typed languages).

In trying to write a Alexa SmartHome skill I need to create the JSON structures defined (eg) at https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-contactsensor.html

The StateReport Response is where I start to have trouble, in particular with the context.

The example looks like:

  "context": {
    "properties": [
      {
        "namespace": "Alexa.ContactSensor",
        "name": "detectionState",
        "value": "NOT_DETECTED",
        "timeOfSample": "2017-02-03T16:20:50.52Z",
        "uncertaintyInMilliseconds": 0
      },
      {
        "namespace": "Alexa.EndpointHealth",
        "name": "connectivity",
        "value": {
          "value": "OK"
        },
        "timeOfSample": "2017-02-03T16:20:50.52Z",
        "uncertaintyInMilliseconds": 0
      }
    ]
  }

At first glance this looks simple enough:

    Context struct {
        Properties []struct {
            Namespace                 string    `json:"namespace"`
            Name                      string    `json:"name"`
            Value                     string    `json:"value"`
            Timeofsample              time.Time `json:"timeOfSample"`
            Uncertaintyinmilliseconds int       `json:"uncertaintyInMilliseconds"`
        } `json:"properties"`
    } `json:"context"`

But the "Value" field is where I have the problem.

In the first element of the array we have a simple string

        "value": "NOT_DETECTED",

but the second element is a structure

        "value": {
          "value": "OK"
        },

(This doesn't seem to be a typo in the docs; it's repeated at https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-endpointhealth.html and elsewhere as well).

And this is where the strict typing of GoLang and my knowledge of the language begins to defeat me.

How can I model this value element, since it doesn't seem to have a fixed type?

Or is there a better way of doing this?

Create a type like this:

import (
    "bytes"
    "encoding/json"
    "fmt"
)

// nullOK is a JSON OK literal
var nullOK = []byte("OK")

type ValueString struct {
    Error string
    Valid bool
}

// UnmarshalJSON implements json.Unmarshaler.
func (s *ValueString) UnmarshalJSON(data []byte) error {
    if bytes.Equal(data, nullOK) {
        s.Valid = true
        return nil
    }

    if err := json.Unmarshal(data, &s.Error); err != nil {
        return fmt.Errorf("checking OK: couldn't unmarshal JSON: %w", err)
    }

    s.Valid = false
    return nil
}

// MarshalJSON implements json.Marshaler.
func (s ValueString) MarshalJSON() ([]byte, error) {
    if s.Valid {
        return []byte("OK"), nil
    }
    return json.Marshal(s.Error)
}

And use it in your struct:

...
Value                     ValueString    `json:"value"`
...

When value is "OK" your Value.Valid will be true , else check the Value.Error for parsed value.

Using the hint from mkopriva I think I came up with simple proof of concept solution using interface{} and anonymous structures

package main

import "encoding/json"
import "fmt"

type Foo struct {
        Value interface{} `json:"value"`
}

func main() {
        v1 := Foo{Value: "version1"}
        j1, _ := json.Marshal(v1)
        fmt.Println(string(j1))

        v2 := Foo{Value: struct {
                Value string `json:"value"`
        }{Value: "version2"}}
        j2, _ := json.Marshal(v2)
        fmt.Println(string(j2))
}

Running this gives the two ouputs:

{"value":"version1"}
{"value":{"value":"version2"}}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM