Parsing and Serializing YAML in Go Without Struct Definitions

This article is a translated version of my original post on Qiita. Original (Japanese): https://qiita.com/segur/items/677746b7e3d55d0d66b9

Introduction

I wanted to parse YAML and convert it back to a string in Go, but I couldn't find an article that showed how to do it without defining a struct. So I'm writing it down before I forget.

The sample source code is available here: https://github.com/segurvita/yaml-encoding-practice

The YAML Used in This Example

I'm reusing a swagger.yaml from a previous article.

swagger: '2.0'
info:
  description: This is an API for apartments.
  version: 0.0.1
  title: Apartment API
paths:
  '/rooms/{room-id}':
    get:
      summary: Room Info API
      description: Returns information for the specified room-id
      parameters:
        - name: room-id
          in: path
          description: The ID of the room to retrieve
          required: true
          type: integer
          format: int64
      responses:
        '200':
          description: OK
          schema:
            type: object
            properties:
              id:
                type: integer
                format: int64
                example: 404
              comment:
                type: string
                example: Room 404. Maybe it doesn't exist anywhere.

Sample Code

Here is the content of main.go. It converts a YAML string into an object, then converts that object back into a YAML string — not particularly useful on its own, but good for demonstrating the approach.

package main

import "fmt"
import "gopkg.in/yaml.v2"

func main() {
    // Define the input YAML
    yamlInput := []byte(`
swagger: '2.0'
info:
  description: This is an API for apartments.
  version: 0.0.1
  title: Apartment API
paths:
  '/rooms/{room-id}':
    get:
      summary: Room Info API
      description: Returns information for the specified room-id
      parameters:
        - name: room-id
          in: path
          description: The ID of the room to retrieve
          required: true
          type: integer
          format: int64
      responses:
        '200':
          description: OK
          schema:
            type: object
            properties:
              id:
                type: integer
                format: int64
                example: 404
              comment:
                type: string
                example: Room 404. Maybe it doesn't exist anywhere.
  `)

    // Convert YAML to object
    var objInput interface{}
    err := yaml.Unmarshal(yamlInput, &objInput)
    if err != nil {
        fmt.Println("Error: ", err)
    }

    // Convert object to YAML
    yamlOutput, err := yaml.Marshal(&objInput)
    if err != nil {
        fmt.Println("Error: ", err)
    }

    // Print to stdout
    fmt.Println("# ------------------------------------------------------------")
    fmt.Println("# Input YAML:")
    fmt.Println("# ------------------------------------------------------------")
    fmt.Println(string(yamlInput), "\n")
    fmt.Println("# ------------------------------------------------------------")
    fmt.Println("# Object:")
    fmt.Println("# ------------------------------------------------------------")
    fmt.Println(objInput, "\n")
    fmt.Println("# ------------------------------------------------------------")
    fmt.Println("# Output YAML:")
    fmt.Println("# ------------------------------------------------------------")
    fmt.Println(string(yamlOutput), "\n")
}

Explanation

I used go-yaml for YAML processing.

By passing an interface{} variable as the second argument to Unmarshal, we avoid the need to define a struct. Very convenient!

Marshal also works without a struct definition.

Output

Running the code above produces the following output:

info:
  description: This is an API for apartments.
  title: Apartment API
  version: 0.0.1
paths:
  /rooms/{room-id}:
    get:
      description: Returns information for the specified room-id
      parameters:
      - description: The ID of the room to retrieve
        format: int64
        in: path
        name: room-id
        required: true
        type: integer
      responses:
        "200":
          description: OK
          schema:
            properties:
              comment:
                example: Room 404. Maybe it doesn't exist anywhere.
                type: string
              id:
                example: 404
                format: int64
                type: integer
            type: object
      summary: Room Info API
swagger: "2.0"

The keys at each level were sorted alphabetically, but the content is exactly the same as the input YAML.

References

Closing

Easier than I expected — glad it worked out!