Go에서 JSON, HTTP client 써보기
2019-02-21 12:35:23

Go에서 JSON을 사용하려면 먼저 표준 패키지인 ncoding/json 패키지를 import 해야 한다.

JSON

구조체 -> JSON

Go에서 구조체를 JSON 문자열로 변환하는 법부터 적는다.

좀 당황스러웟던 건 구조체의 변수가 첫 글자가 대문자여야 된다.

그리고 JSON 문자열 만들 때 리턴 값이 2개다

첫 번째 리턴은 JSON 문자열 데이터, 2번쨰 리턴은 문자열 만드는게 실패 했을 경우의 에러

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"encoding/json"
"fmt"
)

// User - 유저 구조체
type User struct {
ID string
Name string
Age int
}

func main() {
u := User{}

u.ID = "onepiece"
u.Name = "luffy"
u.Age = 18

uToJSON, err := json.Marshal(u)

if err != nil {
panic(err)
} else {
fmt.Println("uToJSON:", string(uToJSON)) // uToJSON {"ID":"onepiece","Name":"luffy","Age":18}
}
}

위에서 언급한듯이 대문자를 써야 하기 때문에 내 JSON 문자열도 대문자로 들어간다

소문자로 하기 위해선 다음과 같이 해야 함.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"encoding/json"
"fmt"
)

// User2 - 유저2 구조체
type User2 struct {
ID string `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
u2 := new(User2)

u2.ID = "neverland"
u2.Name = "emma"
u2.Age = 10

u2ToJSON, _ := json.Marshal(u2)

fmt.Println("u2ToJSON:", string(u2ToJSON)) // u2ToJSON {"id":"neverland","name":"emma","age":10}
}

저런식으로 정의를 해줘야 함. 좀 귀찮게 되어 잇다;;

그리고 마지막에 string()으로 감싸줘야 문자열로 된다. 안 그러면 바이너리 같은 형식으로 되어 있음.

아래처럼 key안에 또 다른 key/value가 잇는 JSON 같은 경우에는 구조체 안의 구조체로.

1
2
3
4
5
6
{
"aa": "xx",
"bb": {
"z": "z"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"encoding/json"
"fmt"
)

// Anime - 애니매 구조체
type Anime struct {
Title string `json:"title"`
Year int `json:"year"`
Heroine Heroine // 요런식으로 밑의 구조체를 추가
}

// Heroine - 여히로인 *-_-* 구조체
type Heroine struct {
Name string `json:"name"`
Age int `json:"age"`
}

func main() {

ani := new(Anime)

ani.Title = "방패용사 성공담"
ani.Year = 2019
ani.Heroine.Name = "필로"
ani.Heroine.Age = 1

aniToJSON, _ := json.Marshal(ani)

fmt.Println("aniToJSON:", string(aniToJSON)) // aniToJSON {"title":"방패용사 성공담","year":2019,"Heroine":{"name":"필로","age":1}}
}

맵 -> JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"encoding/json"
"fmt"
)

func main() {
var myMap map[string]string

myMap = make(map[string]string)

myMap["a"] = "apple"
myMap["b"] = "banana"

myMapToJSON, _ := json.Marshal(myMap)

fmt.Println("myMapToJSON:", string(myMapToJSON)) // myMapToJSON: {"a":"apple","b":"banana"}
}

인터페이스 -> JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"encoding/json"
"fmt"
)

func main() {
m := map[string]interface{}{
"a": "hi",
"b": true,
"c": 33,
"d": []interface{}{
"array1",
"array2",
},
"e": map[string]interface{}{
"x": "x",
"y": "y",
"z": "z",
},
}

mToJSON, _ := json.Marshal(m)

fmt.Println("mToJSON:", string(mToJSON)) // mToJSON {"a":"hi","b":true,"c":33,"d":["array1","array2"],"e":{"x":"x","y":"y","z":"z"}}
}

JSON 파싱

읽을 샘플 데이터

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"int": 3,
"str": "aaa",
"bool": true,
"arr": [1,2,3],
"obj": {
"x": "x",
"y": "y"
},
"all": {
"obj2": {
"obj3": {
"a": "a",
"b": "b"
},
"bool": false,
"arr2": ["q", "w", "e"]
}
}
}

io/ioutil라는 표준 패키지를 통해 파일을 읽을 수 잇음.

JSON -> 인터페이스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package main

import (
"encoding/json"
"io/ioutil"
"fmt"
)

func main() {
myJSON, err := ioutil.ReadFile("./sample.json")
if err != nil {
panic(err)
} else {
fmt.Println("myJSON:", string(myJSON)) // 위의 샘플 json 문자열인 상태.
}

// 먼저 담을 인터페이스를 만드러준다.
var myJSONparse map[string]interface{}

// 에러 담을 변수(JSON parse 실패 시 리턴.) := json.Unmarshal(JSON 문자열, 담을 인터페이스나 구조체의 포인터)
e := json.Unmarshal(myJSON, &myJSONparse) // 이게 파싱하는 구문.

if e != nil {
panic(e)
} else {
fmt.Println(myJSONparse) // map[int:3 str:aaa bool:true arr:[1 2 3] obj:map[x:x y:y] all:map[obj2:map[obj3:map[a:a b:b] bool:false arr2:[q w e]]]]
}

myInt := myJSONparse["int"]

fmt.Println("myInt:", myInt) // 3

myStr := myJSONparse["str"]

fmt.Println("myStr:", myStr) // aaa

myBool := myJSONparse["bool"]

fmt.Println("myBool:", myBool) // true

all := myJSONparse["all"]

fmt.Println(`myJSONparse["all"]:`, all) // myJSONparse["all"] map[obj2:map[obj3:map[a:a b:b] bool:false arr2:[q w e]]]

// 인터페이스 안의 맵들은 앞에 map 인터페이스를 붙여줘야 된다, 안 하면 에러가 난다..
obj2 := all.(map[string]interface{})["obj2"]

fmt.Println("obj2", obj2) // map[obj3:map[a:a b:b] bool:false arr2:[q w e]]

obj3 := obj2.(map[string]interface{})["obj3"]

fmt.Println("obj3:", obj3) // obj3: map[a:a b:b]

obj3A := obj3.(map[string]interface{})["a"]

fmt.Println("obj3.a:", obj3A) // obj3.a: a

obj3B := obj3.(map[string]interface{})["b"]

fmt.Println("obj3.b:", obj3B) // obj3.b: b

obj3C := obj3.(map[string]interface{})["c"]

fmt.Println("obj3.c:", obj3C) // obj3.c: <nil> 업는 값이니깐

obj2Bool := obj2.(map[string]interface{})["bool"]

fmt.Println("obj2.bool:", obj2Bool) // obj2.bool: false

obj2Arr2 := obj2.(map[string]interface{})["arr2"]

fmt.Println("obj2.arr:", obj2Arr2) // obj2.arr: [q w e]
}

JSON -> 구조체

구조체를 조낸 정의해서 받아야 함.. -_-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package main

import (
"encoding/json"
"io/ioutil"
"fmt"
)

// parseJSON - JSON 1depth
type parseJSON struct {
Int int `json:"int"`
Str string `json:"str"`
Bool bool `json:"bool"`
Arr []int `json:"arr"`
Obj FirstObj `json:"obj"`
All FirstAll `json:"all"`
}

// FirstObj - JSON 1depth obj
type FirstObj struct {
X string `json:"x"`
Y string `json:"y"`
}

// FirstAll - JSON 1depth all
type FirstAll struct {
All SecondObj2 `json:"obj2"`
}

// SecondObj2 - JSON 2depth obj2
type SecondObj2 struct {
Obj3 thirdObj3 `json:"obj3"`
Bool bool `json:"bool"`
Arr2 []string `json:"arr2"`
}

// thirdObj3 - JSON 3depth obj3
type thirdObj3 struct {
A string `json:"a"`
B string `json:"b"`
}

func main() {
myJSON, err := ioutil.ReadFile("./sample.json")
if err != nil {
panic(err)
} else {
fmt.Println("myJSON:", string(myJSON))
}

res := parseJSON{}

parseErr := json.Unmarshal(myJSON, &res)

if parseErr != nil {
panic(parseErr)
} else {
fmt.Println(res) // {3 aaa true [1 2 3] {x y} {{{a b} false [q w e]}}}
}
}

HTTP

확인용으로 Express로 하나 띄움

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const express = require('express');

const app = express();

const bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));

app.listen(3000, () => {
console.log('listen');
});

app.get('/q', (req, res) => {
res.status(200).send({code: 200, message: "hi"});
});

app.get('/q2', (req, res) => {
res.status(403).send({code: 403, message: "403"});
});

app.get('/q3', (req, res) => {
res.status(500).send({code: 500, message: "wrong"});
});

app.get('/q4', (req, res) => {
console.log(req.query);
res.status(200).send({code: 200, message: req.query.id});
});

app.get('/q5', (req, res) => {
console.log('req header: ', req.headers);
console.log('content-type: ', req.headers['content-type']);
res.status(200).send({code: 200, message: "hi"});
});

app.post('/w', (req, res) => {
console.log('body: ', req.body);
res.status(200).send({code: 200, id: req.body.id, name: req.body.name});
});

app.post('/w2', (req, res) => {
console.log('req header: ', req.headers);
console.log('content-type: ', req.headers['content-type']);
res.status(200).send({code: 200, title: req.body.title});
});

내장 패키지 net/http를 통해 가능함.

Get

쿼리스트링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"fmt"
"net/http"
"net/url"
"io/ioutil"
)

func main() {
req2, err := http.NewRequest("GET", "http://localhost:3000/q4", nil)

if err != nil {
panic(err)
}

q := url.Values{}
q.Add("userId", "eqweqw12312")
q.Add("name", "홍길동")
q.Add("api_key", "zfxgrwwewrere")

req2.URL.RawQuery = q.Encode() // 쿼리스트링 이스케이프

client2 := &http.Client{}

res2, err := client2.Do(req2) // 여기가 실제 실행하는 부분.

if err != nil {
panic(err)
}

defer res2.Body.Close()

thisBody2, err := ioutil.ReadAll(res2.Body)

if err != nil {
panic(err)
}

fmt.Println("thisBody2", string(thisBody2)) // thisBody2 {"code":200}
}

본문 받기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"fmt"
"encoding/json"
"io/ioutil"
"net/http"
)

type parseJSON struct {
Code int `json:"code"`
Message string `json:"message"`
}

func main() {
resp, err := http.Get("http://localhost:3000/q")

if err != nil {
panic(err)
}

defer resp.Body.Close() // client exit

fmt.Println("resp", resp) // resp &{200 OK 200 HTTP/1.1 1 1 map[Connection:[keep-alive] X-Powered-By:[Express] Content-Type:[application/json; charset=utf-8] Content-Length:[27] Etag:[W/"1b-yJ5LQLosEB2UsjA+s0n8DgGAfvY"] Date:[Sun, 24 Feb 2019 14:42:31 GMT]] 0xc42013c400 27 [] false false map[] 0xc42010a100 <nil>}

body, err := ioutil.ReadAll(resp.Body) // body를 읽으면 이렇게 해야 된다.

if err != nil {
panic(err)
}

fmt.Println("body", string(body)) // body {"code":200,"message":"hi"}

thisRes := parseJSON{}

parseErr := json.Unmarshal(body, &thisRes) // json parse

if parseErr != nil {
panic(parseErr)
}

fmt.Println(thisRes.Code) // 200
}

POST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"fmt"
"net/http"
"io/ioutil"
"encoding/json"
"bytes"
)

// myParam - JSON 파라미터
type myParam struct {
ID string `json:"id"`
Name string `json:"name"`
}

func main() {
thisParam := myParam{} // 구조체.

thisParam.ID = "SAO"
thisParam.Name = "kirito"

reqParams, _ := json.Marshal(thisParam) // json stringfy()

// json 문자열로 변경을 한 후 byte로 변경을 해줘야 한다. 파라미터에 string이 들어갈수가 업음..

buff := bytes.NewBuffer(reqParams)

fmt.Println("buff", buff) // buff {"id":"SAO","name":"kirito"}

postRes2, err := http.Post("http://localhost:3000/w", "application/json", buff)

if err != nil {
panic(err)
}

defer postRes2.Body.Close()

postResBody2, err := ioutil.ReadAll(postRes2.Body)
if err == nil {
fmt.Println("postResBody2", string(postResBody2)) // postResBody2 {"code":200,"id":"SAO","name":"kirito"}
}
}

공통

헤더 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"net/http"
"io/ioutil"
"bytes"
)

func main() {

var jsonStr = []byte(`{"title":"전생했더니 슬라임이었던 건에 대하여"}`)

req, err := http.NewRequest("POST", "http://localhost:3000/w2", bytes.NewBuffer(jsonStr))

if err != nil {
panic(err)
}

req.Header.Add("content-type", "application/json") // 이런 식으로 헤더에 추가할 수 있다.
req.Header.Add("Accept", "application/json")

client := &http.Client{}

res, err := client.Do(req)

if err != nil {
panic(err)
}

defer res.Body.Close()

thisBody, err := ioutil.ReadAll(res.Body)

if err != nil {
panic(err)
}

fmt.Println("thisBody", string(thisBody)) // thisBody {"code":200,"title":"전생했더니 슬라임이었던 건에 대하여"}
}

상태 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package main

import (
"net/http"
"fmt"
)

func main() {

ch1 := make(chan map[string]interface{}) // 상태를 받기 위한 채널.

ch2 := make(chan map[string]interface{})

ch3 := make(chan map[string]interface{})

go func() {
resp, err := http.Get("http://localhost:3000/q") // http 메소드 GET

if err != nil {
panic(err)
}

defer resp.Body.Close() // http 클라이언트 종료.

r := make(map[string]interface{})

r["statusCode"] = resp.StatusCode // 상태코드.
r["status"] = resp.Status // 상태 메세지.

ch1 <- r

}()

go func() {
resp, err := http.Get("http://localhost:3000/q2")

if err != nil {
panic(err)
}

defer resp.Body.Close() // client exit

r := make(map[string]interface{})

r["statusCode"] = resp.StatusCode
r["status"] = resp.Status

ch2 <- r

}()

go func() {
resp, err := http.Get("http://localhost:3000/q3")

if err != nil {
panic(err)
}

defer resp.Body.Close() // client exit

r := make(map[string]interface{})

r["statusCode"] = resp.StatusCode
r["status"] = resp.Status

ch3 <- r

}()

for index := 0; index < 3; index++ {
select {
case msg1 := <-ch1:
fmt.Println("첫 번쨰 리퀘스트 http status", msg1) // 첫 번쨰 리퀘스트 http status map[statusCode:200 status:200 OK]
case msg2 := <-ch2:
fmt.Println("두 번쨰 리퀘스트 http status", msg2) // 두 번쨰 리퀘스트 http status map[statusCode:403 status:403 Forbidden]
case msg3 := <-ch3:
fmt.Println("세 번쨰 리퀘스트 http status", msg3) // 세 번쨰 리퀘스트 http status map[statusCode:500 status:500 Internal Server Error]
}
}
}
Prev
2019-02-21 12:35:23
Next