I want to unmarshal a geojson string into a suitable struct type. I have three different geojson strings that I want to unmarshal into the same struct:
var jsonBlobPointString = []byte(`{"Type":"Point", "Coordinates":[1.1,2.0]}`)
var jsonBlobLineString = []byte(`{"Type":"LineString", "Coordinates":[[1.1,2.0],[3.0,6.3]]}`)
var jsonBlobPolygonString = []byte(`{"Type":"Polygon", "Coordinates":[[[1.1,2.0],[3.0,6.3],[5.1,7.0],[1.1,2.0]]]}`)
I came up with a struct type that I´m not totally happy with:
type GeojsonType struct {
Type string
Coordinates interface{}
}
See this link for complete example: http://play.golang.org/p/Bt-51BX__A
I would rather not use interface{} for Coordinates. I would instead use somehting that give me some validation for example Coordinates [] float64 for Point and Coordinates[][] float64 for LineString.
Is it possible to create a struct type so that Point, LineString and Polygon all can be represented in Coordinates without using interface?
What you want is to create 3 different types of object from the same json dictionary.
As far as I know that isn't possible, however you can use the RawMessage type to delay the json decoding and use a bit of pre-processing like this
package main
import (
"encoding/json"
"fmt"
)
type Point struct {
Coordinates []float64
}
type Line struct {
Points [][]float64
}
type Polygon struct {
Lines [][][]float64
}
type GeojsonType struct {
Type string
Coordinates json.RawMessage
Point Point
Line Line
Polygon Polygon
}
var jsonBlob = []byte(`[
{"Type":"Point", "Coordinates":[1.1,2.0]},
{"Type":"LineString", "Coordinates":[[1.1,2.0],[3.0,6.3]]},
{"Type":"Polygon", "Coordinates":[[[1.1,2.0],[3.0,6.3],[5.1,7.0],[1.1,2.0]]]}
]`)
func main() {
var geojsonPoints []GeojsonType
err := json.Unmarshal(jsonBlob, &geojsonPoints)
if err != nil {
fmt.Println("error:", err)
}
// Postprocess the coordinates
for i := range geojsonPoints {
t := &geojsonPoints[i]
switch t.Type {
case "Point":
err = json.Unmarshal(t.Coordinates, &t.Point.Coordinates)
case "LineString":
err = json.Unmarshal(t.Coordinates, &t.Line.Points)
case "Polygon":
err = json.Unmarshal(t.Coordinates, &t.Polygon.Lines)
default:
panic("Unknown type")
}
if err != nil {
fmt.Printf("Failed to convert %s: %s", t.Type, err)
}
fmt.Printf("%+v\n", t)
}
}
Which prints
&{Type:Point Coordinates:[91 49 46 49 44 50 46 48 93] Point:{Coordinates:[1.1 2]} Line:{Points:[]} Polygon:{Lines:[]}}
&{Type:LineString Coordinates:[91 91 49 46 49 44 50 46 48 93 44 91 51 46 48 44 54 46 51 93 93] Point:{Coordinates:[]} Line:{Points:[[1.1 2] [3 6.3]]} Polygon:{Lines:[]}}
&{Type:Polygon Coordinates:[91 91 91 49 46 49 44 50 46 48 93 44 91 51 46 48 44 54 46 51 93 44 91 53 46 49 44 55 46 48 93 44 91 49 46 49 44 50 46 48 93 93 93] Point:{Coordinates:[]} Line:{Points:[]} Polygon:{Lines:[[[1.1 2] [3 6.3] [5.1 7] [1.1 2]]]}}
Based on Nick Craig-Wood answer I built the following Marshal/UnMarshal functions
package geojson
//https://stackoverflow.com/questions/15719532/suitable-struct-type-for-unmarshal-of-geojson
import (
"encoding/json"
)
type Point struct {
Coordinates []float64
}
type Line struct {
Points [][]float64
}
type Polygon struct {
Lines [][][]float64
}
type Geojson struct {
Type string `json:"type"`
Coordinates json.RawMessage `json:"coordinates"`
Point Point `json:"-"`
Line Line `json:"-"`
Polygon Polygon `json:"-"`
}
func (g *Geojson) UnmarshalJSON(b []byte) error {
type Alias Geojson
aux := (*Alias)(g)
err := json.Unmarshal(b, &aux)
if err != nil {
return err
}
switch g.Type {
case "Point":
err = json.Unmarshal(g.Coordinates, &g.Point.Coordinates)
case "LineString":
err = json.Unmarshal(g.Coordinates, &g.Line.Points)
case "Polygon":
err = json.Unmarshal(g.Coordinates, &g.Polygon.Lines)
}
g.Coordinates = []byte(nil)
return err
}
func (g Geojson) MarshalJSON() ([]byte, error) {
var raw json.RawMessage
var err error
switch g.Type {
case "Point":
raw, err = json.Marshal(&g.Point.Coordinates)
case "LineString":
raw, err = json.Marshal(&g.Line.Points)
case "Polygon":
raw, err = json.Marshal(&g.Polygon.Lines)
}
if err != nil {
return nil, err
}
g.Coordinates = raw
type Alias Geojson
aux := (*Alias)(&g)
return json.Marshal(aux)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With