神刀安全网

Working With JSON in Go

Working With JSON in Go

Working with JSON in strictly-typed languages (such as Go) can be tricky. Go actually does a remarkable job at this without much effort – like most things in Go.

Go provides native libraries for JSON and it integrates well with the language. There is a lot of great documentation in the encoding/json package.

I will try and cover the 90% cases in this article.

All examples will use the following basic imports so that the same lines don’t need to be repeated:

import (     "encoding/json"     "fmt" )

Table of Contents

Decoding JSON

In a perfect world the schema of the object and the struct definition would both remain the same and have predicable value types. You could say this is easiest scenario, it’s also the most ideal place to start.

json.Unmarshal takes a byte array (we convert a string to []byte ) and a reference to the object you wish to decode the value into.

type Person struct {     FirstName string     LastName  string }  func main() {     theJson := `{"FirstName": "Bob", "LastName": "Smith"}`      var person Person     json.Unmarshal([]byte(theJson), &person)      fmt.Printf("%+v/n", person) }
{FirstName:Bob LastName:Smith}

Go uses the case of the first letter to designate whether the entity is exported or not. However, this does not effect the decoding because it is not sensitive to case. For example {"firstname": "Bob", "lastname": "Smith"} decoded into:

type Person struct {     FirstName string     LastName  string }

Will still map the keys correctly. Just be careful if you need to encode this data back to JSON the keys will change case which will potentially break other systems. There is a simple way around this with tags explained later.

Encoding JSON

Encoding JSON is very easy. We simply provide the variable to be encoded to the json.Marshal function:

type Person struct {     FirstName string     LastName  string }  func main() {     person := Person{"James", "Bond"}     theJson, _ := json.Marshal(person)      fmt.Printf("%+v/n", string(theJson)) }
{"FirstName":"James","LastName":"Bond"}

Mapping Keys

If you need to map completely different JSON keys to a struct you can use tags . Tags are text-based annotations added to variables:

type Person struct {     FirstName string `json:"fn"`     LastName  string `json:"ln"` }  func main() {     theJson := `{"fn": "Bob", "ln": "Smith"}`      var person Person     json.Unmarshal([]byte(theJson), &person)      fmt.Printf("%+v/n", person) }

This also applies to encoding the object so that the keys will be mapped back to their original names.

Default Values

The json.Unmarshal function takes in an object rather than a type. Any values decoded in the JSON will replace values found in the object, but otherwise it will leave the values as they were which is ideal for setting up default properties for your object:

type Person struct {     FirstName string     LastName  string }  func newPerson() Person {     return Person{"<No First>", "<No Last>"} }  func main() {     theJson := `{"FirstName": "Bob"}`     person := newPerson()     json.Unmarshal([]byte(theJson), &person)      fmt.Printf("%+v/n", person) }
{FirstName:Bob LastName:<No Last>}

Handling Errors

If the JSON is unable to be parsed (like a syntax error) the error is returned from the json.Unmarshal function:

func main() {     theJson := `invalid json`     err := json.Unmarshal([]byte(theJson), nil)      fmt.Printf("%+v/n", err) }
invalid character 'i' looking for beginning of value

More commonly is that the JSON values are of a different type than those defined in the Go structures and variables. Here is an example of trying to encode a JSON string into an integer variable:

func main() {     theJson := `"123"`      var value int     err := json.Unmarshal([]byte(theJson), &value)      fmt.Printf("%+v/n", err) }
json: cannot unmarshal string into Go value of type int

How you handle these errors is up to you, but if your dealing with JSON that can be messy or dynamic you need to keep reading…

Dynamic JSON

Dynamic JSON is the most difficult to deal with because you have to investigate the type of data before you can reliably use it.

There are two approaches to dealing with dynamic JSON:

1. Create a flexible skeleton to unserialise into and test the types inside that.

type PersonFlexible struct {     Name interface{} }  type Person struct {     Name string }  func main() {     theJson := `{"Name": 123}`      var personFlexible PersonFlexible     json.Unmarshal([]byte(theJson), &personFlexible)      if _, ok := personFlexible.Name.(string); !ok {         panic("Name must be a string.")     }      // When validation passes we can use the real object and types.     // This code will never be reached because the above will panic()...     // But if you make the Name above a string it will run the following:     var person Person     json.Unmarshal([]byte(theJson), &person)      fmt.Printf("%+v/n", person) }

2. Go totally rogue (pun intended) and investigate the entire value one step at a time.

This is akin to dealing with JSON in JavaScript with lots of typeof conditions. You can do the same thing in Go like above or use thebeauty of the switch:

func main() {     theJson := `123`      var anything interface{}     json.Unmarshal([]byte(theJson), &anything)      switch v := anything.(type) {     case float64:         // v is an float64         fmt.Printf("NUMBER: %f/n", v)      case string:         // v is a string         fmt.Printf("STRING: %s/n", v)      default:         panic("I don't know how to handle this!")     } }

Important:Go uses fixed types for JSON values. Even though you would think 123 would be decoded as some type of int it will actually always be a float64 . This simplifies the type switches but may require you to also round or test for an actual integer value if that’s a requirement.

Validating JSON Schemas

If you have a complex JSON structure and/or more complex rules about how that data should be formatted it is easier to use the JSON Schema format to do the validation for you.

I won’t go into too much detail here because JSON Schema options could fill more than a whole article. However, here is an quick example using the xeipuuv/gojsonschema package with the example they provide:

import (     "fmt"     "github.com/xeipuuv/gojsonschema" )  func main() {     schemaLoader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")     documentLoader := gojsonschema.NewReferenceLoader("file:///home/me/document.json")      result, err := gojsonschema.Validate(schemaLoader, documentLoader)     if err != nil {         panic(err.Error())     }      if result.Valid() {         fmt.Printf("The document is valid/n")     } else {         fmt.Printf("The document is not valid. see errors :/n")         for _, desc := range result.Errors() {             fmt.Printf("- %s/n", desc)         }     } }

Thank you for reading. I’d really appreciate any and all feedback, please leave your comments below or consider subscribing. Happy coding.

Elliot Chance –

I’m a data nerd and TDD enthusiast living in Sydney, Australia. I love exploring new technologies and working on modern ways to solve age old problems.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Working With JSON in Go

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址