Skip to content

Commit

Permalink
Episode 19 - Parsing Arbitrary JSON (#145)
Browse files Browse the repository at this point in the history
* Episode 19 - consuming unknown JSON input

* dropping use of go-simplejson

* updating docs to reflect not using go-simplejson

* bare link to simplejson

* Adding web page for parsing arbitrary JSON
  • Loading branch information
arschles committed Feb 20, 2018
1 parent 2c9df78 commit a1709c3
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ glide-v0.12.0-linux-amd64.tar.gz
*.prof
episode*/vendor
episode*/episode*
episode*/gifm
www/public
episode19/vendor
4 changes: 4 additions & 0 deletions episode19/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build:
go build -o gifm
run:
./gifm
18 changes: 18 additions & 0 deletions episode19/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Consuming Unknown JSON Input

Go in 5 Minutes, episode 19, as requested by [@tmcarr](https://github.com/tmcarr)!

This screencast teaches how to consume some unknown JSON. We don't know what it looks like
ahead of time, and we have to handle anything that comes our way.

We'll be using only the standard library for this, although we could also use some wonderful
packages out there to help. My favorite so far is Bitly's
[go-simplejson](https://godoc.org/github.com/bitly/go-simplejson).
I've always done this type of parsing myself, but I've started using `go-simplejson` and really
like it. It's well designed and very easy to use!

# Outline

1. Basics of JSON Parsing in Go
2. Handling different types with recursion :)
3. Let's see an example!
8 changes: 8 additions & 0 deletions episode19/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

import (
"log"
"os"
)

var logger = log.New(os.Stdout, "", log.Lshortfile)
95 changes: 95 additions & 0 deletions episode19/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

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

func main() {
if js, err := unmarshal(aDict); err != nil {
logger.Fatalf("error decoding (%s)", err)
} else {
if str, err := ifaceToString(js); err != nil {
logger.Fatalf("error converting js (%s)", err)
} else {
logger.Println(str)
}
}

if js, err := unmarshal(aList); err != nil {
logger.Fatalf("error decoding (%s)", err)
} else {
if str, err := ifaceToString(js); err != nil {
logger.Fatalf("error converting js (%s)", err)
} else {
logger.Println(str)
}
}

if js, err := unmarshal(anInt); err != nil {
logger.Fatalf("error decoding (%s)", err)
} else {
if str, err := ifaceToString(js); err != nil {
logger.Fatalf("error converting js (%s)", err)
} else {
logger.Println(str)
}
}

if js, err := unmarshal(aString); err != nil {
logger.Fatalf("error decoding (%s)", err)
} else {
if str, err := ifaceToString(js); err != nil {
logger.Fatalf("error converting js (%s)", err)
} else {
logger.Println(str)
}
}
}

func unmarshal(str string) (interface{}, error) {
var iface interface{}
decoder := json.NewDecoder(strings.NewReader(str))
decoder.UseNumber()
if err := decoder.Decode(&iface); err != nil {
return nil, err
}
return iface, nil
}

func ifaceToString(iface interface{}) (string, error) {
switch t := iface.(type) {
case map[string]interface{}:
strs := make([]string, len(t))
i := 0
for key, val := range t {
str, err := ifaceToString(val)
if err != nil {
return "", err
}
strs[i] = fmt.Sprintf("%s: %s", key, str)
i++
}
return "{" + strings.Join(strs, ", ") + "}", nil
case []interface{}:
strs := make([]string, len(t))
i := 0
for _, val := range t {
str, err := ifaceToString(val)
if err != nil {
return "", err
}
strs[i] = str
i++
}
return "[" + strings.Join(strs, ", ") + "]", nil
case int:
return fmt.Sprintf("%d", t), nil
case json.Number:
return fmt.Sprintf("%s", t), nil
case string:
return fmt.Sprintf(`"%s"`, t), nil
}
return "", fmt.Errorf("unsupported value %#v (%T)", iface, iface)
}
13 changes: 13 additions & 0 deletions episode19/unknown_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

const (
aDict = `{
"an_int": 1,
"a_string": "abc",
"an_array": [1, 2, "3"],
"a_dict": {"a": 1, "b": 2}
}`
aList = `[1, "2", 3, {"four": 4}]`
anInt = `1`
aString = `"1"`
)
29 changes: 29 additions & 0 deletions www/content/screencast/episode_19_parsing_arbitrary_json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
+++
date = "2018-02-19T11:44:01-08:00"
title = "Parsing Arbitrary JSON"
type = "screencast"

+++

_Episode 19_

[@tmcarr](https://github.com/tmcarr) requested in
[#125](https://github.com/arschles/go-in-5-minutes/issues/125) that I do a screencast
on how to consume json with unknown fields.

This screencast shows how to exactly that! I'll show how to parse JSON using just the standard
library, and also point folks to a package that will help if you don't want to steal my code
(which is totally fine by the way!)

Here ya go [@tmcarr](https://github.com/tmcarr)!

<!--more-->

If you like what you see here, consider
[subscribing to the GIFM newsletter](https://www.goin5minutes.com/subscribe/).

Keep on rockin', Gophers!

<iframe width="560" height="315" src="https://www.youtube.com/embed/52yMK6p_cAg" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>

Check out the example code [on Github](https://github.com/arschles/go-in-5-minutes/tree/master/episode19).

0 comments on commit a1709c3

Please sign in to comment.