Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
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
Archives
Today
Total
관리 메뉴

Passion, Grace & Fire.

번역 : Working With JSON in Go 본문

golang

번역 : Working With JSON in Go

vincenthanna 2019. 5. 30. 12:10

다음 포스트의 내용을 한글로 옮긴 것입니다 :

http://elliot.land/working-with-json-in-go by Elliot Chance

JSON을 엄격한 형식의 언어(Go와 같은)에서 다루는 것은 까다로울 수 있다. 나는 json과 네이티브 자료형 사이의 변환에 대해 말하는 것이고, 그래서 많은 type checking과 런타임 type casting을 발생시키는 기본 배열과 사전 자료형을 통해 json에 접근하는 것에 대해서는 직접 처리할 필요가 없다는 것이다.

Go는 Go의 많은 다른 것과 마찬가지로 편리하게 JSON을 다룰 수 있도록 많은 작업을 대신 처리해 준다.

Go는 JSON을 위해 내장된 라이브러리를 제공하고 언어와 잘 통합되어 있다. encoding/json 패키지에서 JSON을 다루는 것에 대한 훌륭한 문서들을 많이 찾을 수 있다.

여기서 90%이상의 내용을 커버할 예정이다.

모든 예제는 다음의 기본 import를 사용하기 때문에 이후에 반복해서 적지 않을 것이다.


import (
    "encoding/json"
    "fmt"
)

Decoding JSON

완벽한 세상에서 오브젝트의 스키마와 구조체 정의 이 둘은 동일하게 유지되며 확실한 value type을 가질 것이다. 이게 가장 쉽고 시작하기에 알맞은 시나리오라 할 수 있다.

json.Unmarshal() 은 바이트 배열(여기서는 문자열을 바이트 배열로 변환해서 사용할 것이다.)과 디코딩 완료된 결과를 채워넣을 오브젝트에 대한 참조를 인자로 받는다.

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는 개체가 외부로 노출되어야 하는지 여부를 첫번째 글자의 대소문자 여부에 따라 결정한다. 그러나 디코딩에는 대소문자 여부와 상관없이 이름만 동일하면 해당 개체로 들어간다.

예를 들어

{“firstname”:”Bob”, “lastname”:”Smith”}

가 다음 구조체

type Person struct {
    FirstName string
    LastName string
}

로 디코딩되는 경우에도 키들이 정확히 매핑된다. 단 데이터를 JSON으로 인코딩할 경우키의 대소문자가 변경될 수 있고 잠재적으로 시스템을 망가뜨릴 수 있으므로 주의한다.
태그를 이용해서 그런 문제를 간단히 피할 수 있다.

Encoding JSON

JSON으로 인코딩하는 것은 아주 쉽다. 인코딩할 객체를 json.Marshal() 메소드로 전달하기만 하면 된다.

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

완전히 다른 이름을 가진 JSON 키를 객체의 엔트리에 매핑해야 한다면 변수에 붙는 주석인 태그 를 사용할 수 있다.

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)
}

객체를 인코딩할 경우 개체에 태그가 있으면 키로 지정된다.

Default Values

json.Unmarshal()함수는 타입이 아닌 오브젝트를 인자로 받는다. JSON에서 디코딩된 값들은 키로 검색된 개체를 덮어쓰고 그 외의 개체들은 기존값이 유지되는데, 이것은 객체의 기본값을 정하는 데에는 이상적이다.

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

JSON 데이터를 파싱할 수 없다면(문법 에러의 경우처럼) json.Unmarshal()함수로부터 error가 리턴된다.

func main() {
    theJson := `invalid json`
    err := json.Unmarshal([]byte(theJson), nil)

    fmt.Printf("%+v\n", err)
}
invalid character 'i' looking for beginning of value

JSON의 값들은 Go구조체와 변수에 정의된 것과는 다른 타입인 경우가 일반적이다. JSON문자열을 integer값으로 디코딩하려는 경우 다음과 같이 panic이 발생하게 된다.

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

이런 에러를 핸들링하는 것은 당신에게 달렸지만 다음 단락에서 JSON 타입에 따라 동적으로 처리하는 방법을 확인할 수 있을 것이다.

Dynamic JSON

동적 JSON은 처리하기가 까다롭다. 사용하기 전에 데이터의 타입부터 확인해야 하기 때문이다.
동적으로 JSON을 다루는 것에는 두 가지 접근 방법이 있다.

1.역-직렬화할 유연한 뼈대를 먼저 만들고 내부 개체의 타입을 확인한다.


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.전체 값을 한번에 한 스텝씩 조사해 들어간다.

이것은 JavaScript에서 typeof 연산자를 통해 JSON을 처리하는 것과 유사하다. 동일한 행동을 위와 같이 하거나 switch의 특수 구문을 통해 할 수 있다.

(switch문에서 type assertion 구문을 사용할 경우 case에서 type을 명시해서 처리하는 특수 구문)

(역주 : 원래 예제가 간단하여 예시가 될 수 있는 코드로 교체하였습니다.)

func testDynamic2() {
    theJson := `{
        "glossary": {
            "title": "example glossary",
            "quantity":500,
            "GlossDiv": {
                "title": "S",
                "name" : "Name",
                "id" : 38
            }
        }
    }`

    var anything interface{}
    json.Unmarshal([]byte(theJson), &anything)

    searchStep("", anything, 0 /*indent step*/)
}

// 재귀호출하면서 key/value 쌍을 출력한다
func searchStep(key string, value interface{}, step int) {
    switch value.(type) {
    case float64:
        // v is an float64
        for tab := 0; tab < step; tab++ {
            fmt.Printf("    ")
        }
        fmt.Println(key, ":", value)

    case string:
        // v is a string
        for tab := 0; tab < step; tab++ {
            fmt.Printf("    ")
        }
        fmt.Printf("%v : \"%v\"\n", key, value)

    case map[string]interface{}:
        // map이라면
        for tab := 0; tab < step; tab++ {
            fmt.Printf("    ")
        }
        if len(key) > 0 {
            fmt.Println("<<<", key, ">>>", " : ")
        }
        for k, v := range value.(map[string]interface{}) { //i에는 key가, v에는 value가 들어간다.
            searchStep(k, v, step+1)
        }

    default:
        panic("I don't know how to handle this!")
    }
}

중요 : Go는 JSON값에 대해 fixed type을 사용한다. 123이 일종의 정수로 디코딩 될 것 같지만 실제로는 float64로 된다. 이런 동작이 타입 변환을 간소화시키지만, 필요하다면 반올림이나 타입 체크가 필요할 수 있다.

Validating JSON Schemas

만약 당신이 복잡한 JSON구조체를 가지고 있거나 데이터 포맷에 대한 더 복잡한 규칙를 가지고 있다면, JSON Schema를 사용하면 좋다.

JSON 스키마 옵션이 전체 기사를 채울 수도 있으므로 깊게 들어가지 않겠지만 아래 xeipuuv/gojsonschema 패키지를 사용하는 간단한 예제를 참고하기 바란다.

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)
        }
    }
}
Comments