5802


GAE/Goでjsonを受け取り・返却するサーバーを作成しています。

さて、GAEのdatastoreにはGeographical point型(appengine.GeoPoint)がありますが、  
これを以下のように構造体定義すると、GeoPointのLat, Lngの部分だけjsonのkey名を定義できません。

[code]

    type MapItem struct {
        Id          string             `datastore:"-"`
        Name        string             `json:"name"`
        Description string             `json:"description"`
        Coords      appengine.GeoPoint `json:"coords"`
        Order       int                `json:"order"`
    }
    
[/code]

JSONで以下のようなデータを送れば、GAE/Goではうまく組み立ててくれるので良いのですが、  
出来ることならばLat,Lngの部分をlatitude,longitudeとして与えたく思います。

[code]

    {
        'name': "John Doe",
        'description': "Description...",
        'order': 123,
        'coords': {
            'Lat': 34.12345678, // ← latitude としたい
            'Lng': 135.1234567  // ← longitude としたい
        }
    }
    
[/code]

このような場合、どうすれば良いでしょうか。ご回答頂けますと幸いです。


CC BY-SA 3.0
2015-02-02T14:02:50.390
5794
2015-02-02T17:18:12.230
2684
question
go google-app-engine
appengine.GeoPointにjsonタグを付与したい


`appengine.GeoPoint` は別パッケージ内で定義されていて、直接手がでない。タグ付きの `struct`
は、型が異なるとされるので変換もちょっと難しい。そういうときは、 `type` で新しい型を作ってしまえば良いと思います。  

[code]

    type GeoPointJson appengine.GeoPoint
    
[/code]

で、 `GeoPointJson` を定義。その型に Unmarshaller と、 Marshaller
を定義してやれば、コードが冗長がちになりますが、安全に `GeoPoint` を拡張しつつ、それを自分の JSON 用 `struct`
に組み込むことができます。

[code]

    func (g *GeoPointJson) UnmarshalJSON(b []byte) (err error)
    ... 
    func (g *GeoPointJson) MarshalJSON() ([]byte, error)
    ...
    
[/code]

**実証コード** : <https://play.golang.org/p/9N_ymtEJIn>

[code]

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    // ------------------------------
    // Here, inside another package.
    type GeoPoint struct {
        Lat, Lng float64
    }
    // ------------------------------
    
    type GeoPointJson GeoPoint
    
    type MapItem struct {
        Id          string       `json:"-"`
        Name        string       `json:"name"`
        Description string       `json:"description"`
        Coords      GeoPointJson `json:"coords"`
        Order       int          `json:"order"`
    }
    
    func (g *GeoPointJson) UnmarshalJSON(b []byte) (err error) {
        var jm map[string]float64
        if err = json.Unmarshal(b, &jm); err == nil {
            g.Lat = jm["latitude"]
            g.Lng = jm["longtitude"]
        }
        return
    }
    
    func (g *GeoPointJson) MarshalJSON() ([]byte, error) {
        jm := make(map[string]float64)
        jm["latitude"] = g.Lat
        jm["longtitude"] = g.Lng
        return json.Marshal(jm)
    }
    
    func main() {
        encoded := `
        {
            "name": "John Doe",
            "description": "Description...",
            "order": 123,
            "coords": {
                "latitude": 34.12345678,
                "longtitude": 135.1234567 
            }
        }`
    
        mapItem := &MapItem{}
        if err := json.Unmarshal([]byte(encoded), mapItem); err != nil {
            panic(err)
            return
        }
        fmt.Println(mapItem.Name)
        fmt.Println(mapItem.Description)
        fmt.Println(mapItem.Coords.Lat)
        fmt.Println(mapItem.Coords.Lng)
    
        fmt.Println("----------------")
    
        mapItem.Name = "Modified Name"
        mapItem.Coords.Lat = 100.0
        mapItem.Coords.Lng = 200.0
        if jb, err := json.Marshal(mapItem); err == nil {
            fmt.Println(string(jb[:]))
        }
    }
    
[/code]

Output:

[code]

    John Doe
    Description...
    34.12345678
    135.1234567
    ----------------
    {"name":"Modified Name","description":"Description...","coords":{"latitude":100,"longtitude":200},"order":123}
    
[/code]

### 補足: もう一つのやり方

[code]

    type MapItem struct {
        Id          string `json:"-"`
        Name        string `json:"name"`
        Description string `json:"description"`
        Coords      struct {
            Latitude  float64 `json:"latitude"`
            Longitude float64 `json:"longitude"`
        } `json:"coords"`
        Order int `json:"order"`
    }
    
[/code]

と定義して、

[code]

    geoPoint := *(*GeoPoint)(unsafe.Pointer(&mapItem.Coords))
    
[/code]

のように、メンバ `Coords` を直接 `GeoPoint`
にキャストする方法もあります。こっちの方がコードが少ない（けど危なかしい）それでも良いなら、
<https://play.golang.org/p/Qc60-KsYKl> にサンプルコードを起きましたので、参考にしてください。


CC BY-SA 3.0
2015-02-02T17:18:12.230
5802
2015-02-02T17:18:12.230
4978
5794
answer