Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
14
Help us understand the problem. What is going on with this article?
@catatsuy

GoでJSONのnullをいい感じに扱いたい

More than 3 years have passed since last update.

GoでJSONのnullをいい感じに扱いたいことがあるとします。しかしGoではnullを扱うのは容易ではありません。

Goにはnilが存在しますが、これはポインタ型でしか使えません。よってintstring型では使用できません。Goはintstring型は初期化しなかった場合、ゼロ値に初期化されます。intのゼロ値は0、stringのゼロ値は空文字列です。そのためJSONのnullをGoで扱おうとした場合、Goのゼロ値との区別ができません。

同じ問題はSQLでもあります。nullが存在するカラムから値を取得した際にnullとGoのゼロ値を区別する必要があります。そこでGoのdatabase/sqlではNullStringのようなstructが定義されています。

type NullString struct {
    String string
    Valid  bool // Valid is true if String is not NULL
}

Validの真偽値を確認することで空文字列とnullを区別することができます。これと同じことをJSONでやるにはどうしたらよいでしょうか。

json.Marshaljson.Unmarshalの動きを変更するには以下のインタフェースを実装します。

interface.go
type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

そこで以下のような実装をしてみました。

package main

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

type NullSegments struct {
    Segments Segments
    Valid    bool // Valid is true if Segments is not NULL
}

type Range struct {
    From int `json:"from"`
    To   int `json:"to"`
}

type Segments struct {
    Status int   `json:"status"`
    Range  Range `json:"range"`
}

type Settings struct {
    Segments NullSegments `json:"segments"`
}

var nullLiteral = []byte("null")

func (s *NullSegments) UnmarshalJSON(b []byte) error {
    if bytes.Equal(b, nullLiteral) {
        return nil
    }

    err := json.Unmarshal(b, &s.Segments)
    if err == nil {
        s.Valid = true
        return nil
    }

    return err
}

func (s NullSegments) MarshalJSON() ([]byte, error) {
    if s.Valid {
        return json.Marshal(s.Segments)
    } else {
        return nullLiteral, nil
    }
}

func main() {
    b := []byte(`{"segments": {"status": 1, "range": {"from": 10, "to": 20}}}`)
    // b := []byte(`{"segments": null }`)

    s := &Settings{}

    json.Unmarshal(b, s)

    bb, _ := json.Marshal(s)

    fmt.Println(string(bb))
}

注意点としては[]bytestringにキャストするのはコストが高いのでbytes.Equalなどのメソッドを使います。

bool型のゼロ値はfalseなのでnullが来たときは何もしなくて大丈夫です。null以外が来た場合にValidをtrueにする必要があります。

14
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
catatsuy

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
14
Help us understand the problem. What is going on with this article?