Go jsonデータの処理

14502 ワード

jsonデータフォーマット
jsonデータフォーマットの説明を参照してください.
jsonデータを操作したことがない場合は、上記の文章を見て、本稿の後の内容を理解するのに役立つことをお勧めします.
Go jsonパッケージ
Marshal():Goデータオブジェクト->jsonデータUnMarshal():Jsonデータ->Goデータオブジェクト
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error

jsonデータの構築
Marshal()およびMarshalIndent()関数は、データをjsonデータにカプセル化することができる.
  • struct、slice、array、mapはjson
  • に変換できます.
  • structがjsonに変換されると、フィールドの頭文字が大文字のものだけが
  • に変換されます.
  • map変換の場合、keyはstring
  • でなければなりません.
  • がカプセル化する場合、ポインタであれば、ポインタが指すオブジェクトを追跡してカプセル化
  • を行う.
    例:
    struct構造があります.
    type Post struct {
        Id      int
        Content string
        Author  string
    }

    この構造はブログ記事タイプを表しており,記事ID,記事内容,記事の提出者がある.これは何も言うことはありません.唯一示す必要があるのは、structであり、structはJSONデータにカプセル化(符号化)することができます.
    このstructデータをjsonに変換するには、Marshal()を使用します.次のようになります.
    post := &Post{1, "Hello World", "userA"}
    b, err := json.Marshal(post)
    if err != nil {
        fmt.Println(nil)
    }

    Marshal()は[]byte型を返し、変数bは[]byte型のjsonデータを格納し、出力することができる.
    fmt.Println(string(b))

    結果:
    {"Id":1,"Content":"Hello World","Author":"userA"}

    jsonにカプセル化するときに「美化」を行い、MarshalIndent()を使用すると、接頭辞(接頭辞文字列は一般的に空に設定されています)とインデントを自動的に追加できます.
    c,err := json.MarshalIndent(post,"","\t")
    if err != nil {
        fmt.Println(nil)
    }
    fmt.Println(string(c))

    結果:
    {
        "Id": 1,
        "Content": "Hello World",
        "Author": "userA"
    }

    structを除いてarray、slice、map構造はjsonに解析できますが、mapがjsonに解析される場合、keyはstringのみでなければなりません.これはjson文法の要求です.
    例:
    // slice -> json
    s := []string{"a", "b", "c"}
    d, _ := json.MarshalIndent(s, "", "\t")
    fmt.Println(string(d))
    
    // map -> json
    m := map[string]string{
        "a":"aa",
        "b":"bb",
        "c":"cc",
    }
    e,_ := json.MarshalIndent(m,"","\t")
    fmt.Println(string(e))

    結果を返します.
    [
        "a",
        "b",
        "c"
    ]
    {
        "a": "aa",
        "b": "bb",
        "c": "cc"
    }

    struct tagを使用してjsonの構築を支援
    structが変換できるフィールドはすべて頭文字の大文字のフィールドですが、jsonで小文字の先頭のkeyを使用するにはstructのtagを使用して反射を補助することができます.
    たとえば、Post構造は、頭文字小文字のフィールドcreateAtを追加します.
    type Post struct {
        Id      int      `json:"ID"`
        Content string   `json:"content"`
        Author  string   `json:"author"`
        Label   []string `json:"label"`
    }
    
    
    postp := &Post{
        2,
        "Hello World",
        "userB",
        []string{"linux", "shell"},
        }
    
    p, _ := json.MarshalIndent(postp, "", "\t")
    fmt.Println(string(p))

    結果:
    {
        "ID": 2,
        "content": "Hello World",
        "author": "userB",
        "label": [
            "linux",
            "shell"
        ]
    }

    struct tagを使用する場合、いくつかの注意点があります.
  • tagで識別する名前はjsonデータのkeyと呼ばれる値
  • である.
  • tagは、このフィールド名の頭文字が大文字であっても、このフィールドがjsonデータに変換されないことを示すために`json:"-"`に設定することができる.
  • jsonkeyの名前を文字"-"にするには、`json:"-,"`、すなわちカンマ
  • を特殊に処理することができる.
  • tagに,omitemptyのオプションがある場合、このフィールドの値が0の値、すなわちfalse、0、"、nilなどである場合、このフィールドはjsonの
  • に変換されません.
  • フィールドのタイプがbool、string、intクラス、floatクラスであり、tagに,stringオプションがある場合、このフィールドの値はjson文字列
  • に変換される.
    例:
    type Post struct {
        Id      int      `json:"ID,string"`
        Content string   `json:"content"`
        Author  string   `json:"author"`
        Label   []string `json:"label,omitempty"`
    }

    jsonデータをstructに解析する(構造は既知)
    jsonデータはstructまたは空のインタフェースinterface{}に解析することができる(slice、mapなどであってもよい).上でjsonを構築する際のtag規則を理解すると,jsonの解析を理解するのは簡単である.
    たとえば、次のjsonデータは次のとおりです.
    {
        "id": 1,
        "content": "hello world",
        "author": {
            "id": 2,
            "name": "userA"
        },
        "published": true,
        "label": [],
        "nextPost": null,
        "comments": [{
                "id": 3,
                "content": "good post1",
                "author": "userB"
            },
            {
                "id": 4,
                "content": "good post2",
                "author": "userC"
            }
        ]
    }

    このjsonデータを分析します.
  • 最上位の括弧は匿名のオブジェクトを表し、Goにマッピングされたstructは、このstruct名がPost
  • であると仮定する
  • の最上位カッコにはPost構造のフィールドがあります.これらのフィールドはjsonデータなので、頭文字を大文字にし、tagを設定する必要があります.tagの名前は
  • と小文字にします.
  • authorはサブオブジェクトであり、Goにマッピングされるのは別のstructであり、Postではこのフィールドの名前はAuthorであり、名前はstructの名前と同じであると仮定し、Author
  • でもある.
  • labelは配列であり、Goにマッピングされるのはsliceであってもarrayであってもよく、json arrayが空であるため、Goのslice/arrayタイプは不定であり、例えばintであってもよいし、stringであってもよいし、interface{}であってもよい.ここでの例では、ラベルはstring
  • であることが知られている.
  • nextPostはサブオブジェクトであり、Goにマッピングされるのはstructであるが、jsonではこのオブジェクトがnullであり、このオブジェクトが存在しないことを示すため、Goにマッピングされるstructのタイプは判断できない.しかし、ここでの例では、次の記事はないので、そのタイプもPostタイプ
  • であるべきである.
  • commentはサブオブジェクトであり、配列に囲まれてGoにマッピングされ、slice/arrayであり、slice/arrayのタイプはstruct
  • である.
    解析後,structとstructのtagを対応的に構築することは容易である.以下に、上記の分析に基づいて構築されたデータ構造を示します.
    type Post struct {
        ID        int64         `json:"id"`       
        Content   string        `json:"content"`  
        Author    Author        `json:"author"`   
        Published bool          `json:"published"`
        Label     []string      `json:"label"`    
        NextPost  *Post         `json:"nextPost"` 
        Comments  []*Comment    `json:"comments"` 
    }
    
    type Author struct {
        ID   int64  `json:"id"`  
        Name string `json:"name"`
    }
    
    type Comment struct {
        ID      int64  `json:"id"`     
        Content string `json:"content"`
        Author  string `json:"author"` 
    }

    なお、jsonデータの構築については前述したようにポインタが追跡されるので、ここで反転したstructでポインタタイプを使うのは問題ありません.
    そこで,上記のjsonデータをPostタイプのオブジェクトに解析し,このjsonデータがa.jsonファイルに格納されていると仮定する.コードは次のとおりです.
    func main() {
        //   json  
        fh, err := os.Open("a.json")
        if err != nil {
            fmt.Println(err)
            return
        }
        defer fh.Close()
        //   json  ,   jsonData 
        jsonData, err := ioutil.ReadAll(fh)
        if err != nil {
            fmt.Println(err)
            return
        }
        
        var post Post
        //   json   post 
        err = json.Unmarshal(jsonData, &post)
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println(post)
    }

    出力結果:
    {1 hello world {2 userA} true []  [0xc042072300 0xc0420723c0]}

    jsonデータからstructがどれだけ複雑なのかを逆算するのは、論理的には難しくないが、データが複雑であれば、非常に気持ち悪いことだと感じているかもしれない.だから、他人が書いたツールを使って自動的に変換しましょう.この文書の後ろにはjsonからデータ構造への自動変換ツールが推奨されています.
    解析jsonからinterface(構造不明)
    上記は既知のjsonデータ構造の解析方式であり,json構造が未知であるか構造が変化する可能性がある場合structに解析するのは不合理である.このとき、空のインタフェースinterface{}またはmap[string]interface{}のタイプに解析することができ、この2つのタイプの結果は完全に一致する.interface{}に解析した場合,GoタイプとJSONタイプの対応関係は以下のようになる.
      JSON               Go                  
    ---------------------------------------------
    JSON objects      map[string]interface{} 
    JSON arrays       []interface{}          
    JSON booleans     bool                   
    JSON numbers      float64                
    JSON strings      string                 
    JSON null         nil                    

    例:
    func main() {
        //   json  
        fh, err := os.Open("a.json")
        if err != nil {
            fmt.Println(err)
            return
        }
        defer fh.Close()
        jsonData, err := ioutil.ReadAll(fh)
        if err != nil {
            fmt.Println(err)
            return
        }
        
        //            json  
        var unknown interface{}
        //  :map[string]interface{}         
        err = json.Unmarshal(jsonData, &unknown)
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println(unknown)
    }

    出力結果:
    map[nextPost: comments:[map[id:3 content:good post1
    author:userB] map[id:4 content:good post2 author:userC]]
    id:1 content:hello world author:map[id:2 name:userA] published:true label:[]]

    上にmap構造が出力されます.これは,タイプ対応関係で既に説明されているため,json objectがGo interfaceに解析した場合,map構造に対応する.上から出力された構造をフォーマットすると、次のような構造になります.
    map[
        nextPost:
        comments:[
            map[
                id:3
                content:good post1
                author:userB
            ]
            map[
                id:4
                content:good post2
                author:userC
            ]
        ]
        id:1
        content:hello world
        author:map[
            id:2
            name:userA
        ]
        published:true
        label:[]
    ]

    現在,このmapからタイプを判断し,対応する値を取得することができる.しかし、どのようにタイプを判断しますか?タイプブレークスルーを使用するには、次の手順に従います.
    func main() {
        //   json  
        fh, err := os.Open("a.json")
        if err != nil {
            fmt.Println(err)
            return
        }
        defer fh.Close()
        jsonData, err := ioutil.ReadAll(fh)
        if err != nil {
            fmt.Println(err)
            return
        }
        
        //   json   interface{}
        var unknown interface{}
        err = json.Unmarshal(jsonData, &unknown)
        if err != nil {
            fmt.Println(err)
            return
        }
    
        //     , switch  
        m := unknown.(map[string]interface{})
        for k, v := range m {
            switch vv := v.(type) {
            case string:
                fmt.Println(k, "type: string
    value: ", vv) fmt.Println("------------------") case float64: fmt.Println(k, "type: float64
    value: ", vv) fmt.Println("------------------") case bool: fmt.Println(k, "type: bool
    value: ", vv) fmt.Println("------------------") case map[string]interface{}: fmt.Println(k, "type: map[string]interface{}
    value: ", vv) for i, j := range vv { fmt.Println(i,": ",j) } fmt.Println("------------------") case []interface{}: fmt.Println(k, "type: []interface{}
    value: ", vv) for key, value := range vv { fmt.Println(key, ": ", value) } fmt.Println("------------------") default: fmt.Println(k, "type: nil
    value: ", vv) fmt.Println("------------------") } } }

    結果は次のとおりです.
    comments type: []interface{}
    value:  [map[id:3 content:good post1 author:userB] map[author:userC id:4 content:good post2]]
    0 :  map[id:3 content:good post1 author:userB]
    1 :  map[id:4 content:good post2 author:userC]
    ------------------
    id type: float64
    value:  1
    ------------------
    content type: string
    value:  hello world
    ------------------
    author type: map[string]interface{}
    value:  map[id:2 name:userA]
    name :  userA
    id :  2
    ------------------
    published type: bool
    value:  true
    ------------------
    label type: []interface{}
    value:  []
    ------------------
    nextPost type: nil
    value:  
    ------------------

    Interfaceからの解析は非常に複雑であり,ネスト構造のために正確な反復遍歴ができない可能性があることが分かった.この場合、サードパーティ製パッケージsimplejsonを使用して、後述します.
    jsonフローの解析、作成
    jsonデータを直接解析したり作成したりするだけでなく、ストリームデータを処理したりすることもできます.
  • type Decoder復号jsonからGoデータ構造
  • type Encoder符号化Goデータ構造からjson

  • 例:
    const jsonStream = `
        {"Name": "Ed", "Text": "Knock knock."}
        {"Name": "Sam", "Text": "Who's there?"}
        {"Name": "Ed", "Text": "Go fmt."}
        {"Name": "Sam", "Text": "Go fmt who?"}
        {"Name": "Ed", "Text": "Go fmt yourself!"}
    `
    type Message struct {
        Name, Text string
    }
    dec := json.NewDecoder(strings.NewReader(jsonStream))
    for {
        var m Message
        if err := dec.Decode(&m); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s: %s
    ", m.Name, m.Text) }

    出力:
    Ed: Knock knock.
    Sam: Who's there?
    Ed: Go fmt.
    Sam: Go fmt who?
    Ed: Go fmt yourself!

    また、例えば、jsonデータを標準入力から読み、復号してNameという要素を削除し、最後に再符号化して標準出力に出力する.
    func main() {
        dec := json.NewDecoder(os.Stdin)
        enc := json.NewEncoder(os.Stdout)
        for {
            var v map[string]interface{}
            if err := dec.Decode(&v); err != nil {
                log.Println(err)
                return
            }
            for k := range v {
                if k != "Name" {
                    delete(v, k)
                }
            }
            if err := enc.Encode(&v); err != nil {
                log.Println(err)
            }
        }
    }

    json回転Goデータ構造ツール推奨
    quicktypeツールは、jsonファイルを様々な言語に対応したデータ構造に簡単に変換できます.
    アドレス:https://quicktype.io
    vscodeに関連するプラグインがあります
  • まずコマンドパネルに「set quicktype target language」と入力jsonをどの言語に変換するかを選択するデータ構造(Goなど)
  • 「open quicktype for json」を入力すると、現在のjsonファイルをstructなどの対応するデータ構造
  • に変換できます.
    変換後は、実際のニーズに合わせてタイプの一部を少し変更するだけです.例えばjsonトップクラスの匿名オブジェクトに対応するstructに名前を設定したり、structに変換できない場合にデータ型を判断するために使用されるinterface{}型も変更されます.
    たとえば、quicktypeツールを使用して、前の例のjsonデータを変換したデータ構造を次に示します.
    type A struct {
        ID        int64         `json:"id"`       
        Content   string        `json:"content"`  
        Author    Author        `json:"author"`   
        Published bool          `json:"published"`
        Label     []interface{} `json:"label"`    
        NextPost  interface{}   `json:"nextPost"` 
        Comments  []Comment     `json:"comments"` 
    }
    
    type Author struct {
        ID   int64  `json:"id"`  
        Name string `json:"name"`
    }
    
    type Comment struct {
        ID      int64  `json:"id"`     
        Content string `json:"content"`
        Author  string `json:"author"` 
    }

    そのうちtype A structのAを自分の名前に変更し、Aのinterface{}も合理的なタイプに変更する必要があります.