Skip to content

Commit f35347a

Browse files
authored
Merge pull request #98 from paulmach/external-geojson
geojson: add support for "external" json encoders/decoders
2 parents cfcb08c + bb8773d commit f35347a

8 files changed

+172
-43
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ for _, f := range fc {
107107
}
108108
```
109109

110+
The library supports third party "encoding/json" replacements
111+
such [github.com/json-iterator/go](https://github.com/json-iterator/go).
112+
See the [geojson](geojson) readme for more details.
113+
110114
## Mapbox Vector Tiles
111115

112116
The [encoding/mvt](encoding/mvt) sub-package implements Marshalling and

geojson/README.md

+46
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,52 @@ fc.ExtraMembers["timestamp"] // == "2020-06-15T01:02:03Z"
6666
// base featureCollection object.
6767
```
6868

69+
## Performance
70+
71+
For performance critical applications, consider a
72+
third party replacement of "encoding/json" like [github.com/json-iterator/go](https://github.com/json-iterator/go)
73+
74+
This can be enabled with something like this:
75+
76+
```go
77+
import (
78+
jsoniter "github.com/json-iterator/go"
79+
"github.com/paulmach/orb"
80+
)
81+
82+
var c = jsoniter.Config{
83+
EscapeHTML: true,
84+
SortMapKeys: false,
85+
MarshalFloatWith6Digits: true,
86+
}.Froze()
87+
88+
CustomJSONMarshaler = c
89+
CustomJSONUnmarshaler = c
90+
```
91+
92+
The above change can have dramatic performance implications, see the benchmarks below
93+
on a 100k feature collection file:
94+
95+
```
96+
benchmark old ns/op new ns/op delta
97+
BenchmarkFeatureMarshalJSON-12 2694543 733480 -72.78%
98+
BenchmarkFeatureUnmarshalJSON-12 5383825 2738183 -49.14%
99+
BenchmarkGeometryMarshalJSON-12 210107 62789 -70.12%
100+
BenchmarkGeometryUnmarshalJSON-12 691472 144689 -79.08%
101+
102+
benchmark old allocs new allocs delta
103+
BenchmarkFeatureMarshalJSON-12 7818 2316 -70.38%
104+
BenchmarkFeatureUnmarshalJSON-12 23047 31946 +38.61%
105+
BenchmarkGeometryMarshalJSON-12 2 3 +50.00%
106+
BenchmarkGeometryUnmarshalJSON-12 2042 18 -99.12%
107+
108+
benchmark old bytes new bytes delta
109+
BenchmarkFeatureMarshalJSON-12 794088 490251 -38.26%
110+
BenchmarkFeatureUnmarshalJSON-12 766354 1068497 +39.43%
111+
BenchmarkGeometryMarshalJSON-12 24787 18650 -24.76%
112+
BenchmarkGeometryUnmarshalJSON-12 79784 51374 -35.61%
113+
```
114+
69115
## Feature Properties
70116

71117
GeoJSON features can have properties of any type. This can cause issues in a statically typed

geojson/feature.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package geojson
22

33
import (
4-
"encoding/json"
54
"fmt"
65

76
"github.com/paulmach/orb"
@@ -50,7 +49,7 @@ func (f Feature) MarshalJSON() ([]byte, error) {
5049
jf.Properties = nil
5150
}
5251

53-
return json.Marshal(jf)
52+
return marshalJSON(jf)
5453
}
5554

5655
// UnmarshalFeature decodes the data into a GeoJSON feature.
@@ -69,7 +68,7 @@ func UnmarshalFeature(data []byte) (*Feature, error) {
6968
// into the orb.Geometry types.
7069
func (f *Feature) UnmarshalJSON(data []byte) error {
7170
jf := &jsonFeature{}
72-
err := json.Unmarshal(data, &jf)
71+
err := unmarshalJSON(data, &jf)
7372
if err != nil {
7473
return err
7574
}

geojson/feature_collection.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ json.Unmarshaler interfaces as well as helper functions such as
77
package geojson
88

99
import (
10-
"encoding/json"
1110
"fmt"
1211
)
1312

@@ -63,15 +62,15 @@ func (fc FeatureCollection) MarshalJSON() ([]byte, error) {
6362
tmp["features"] = fc.Features
6463
}
6564

66-
return json.Marshal(tmp)
65+
return marshalJSON(tmp)
6766
}
6867

6968
// UnmarshalJSON decodes the data into a GeoJSON feature collection.
7069
// Extra/foreign members will be put into the `ExtraMembers` attribute.
7170
func (fc *FeatureCollection) UnmarshalJSON(data []byte) error {
7271
tmp := make(map[string]nocopyRawMessage, 4)
7372

74-
err := json.Unmarshal(data, &tmp)
73+
err := unmarshalJSON(data, &tmp)
7574
if err != nil {
7675
return err
7776
}
@@ -80,17 +79,17 @@ func (fc *FeatureCollection) UnmarshalJSON(data []byte) error {
8079
for key, value := range tmp {
8180
switch key {
8281
case "type":
83-
err := json.Unmarshal(value, &fc.Type)
82+
err := unmarshalJSON(value, &fc.Type)
8483
if err != nil {
8584
return err
8685
}
8786
case "bbox":
88-
err := json.Unmarshal(value, &fc.BBox)
87+
err := unmarshalJSON(value, &fc.BBox)
8988
if err != nil {
9089
return err
9190
}
9291
case "features":
93-
err := json.Unmarshal(value, &fc.Features)
92+
err := unmarshalJSON(value, &fc.Features)
9493
if err != nil {
9594
return err
9695
}
@@ -100,7 +99,7 @@ func (fc *FeatureCollection) UnmarshalJSON(data []byte) error {
10099
}
101100

102101
var val interface{}
103-
err := json.Unmarshal(value, &val)
102+
err := unmarshalJSON(value, &val)
104103
if err != nil {
105104
return err
106105
}

geojson/feature_collection_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func TestUnmarshalFeatureCollection(t *testing.T) {
8484

8585
// check unmarshal/marshal loop
8686
var expected interface{}
87-
err = json.Unmarshal([]byte(rawJSON), &expected)
87+
err = unmarshalJSON([]byte(rawJSON), &expected)
8888
if err != nil {
8989
t.Fatalf("unmarshal error: %v", err)
9090
}
@@ -95,7 +95,7 @@ func TestUnmarshalFeatureCollection(t *testing.T) {
9595
}
9696

9797
var raw interface{}
98-
err = json.Unmarshal(data, &raw)
98+
err = unmarshalJSON(data, &raw)
9999
if err != nil {
100100
t.Fatalf("unmarshal error: %v", err)
101101
}

geojson/feature_test.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,19 @@ func TestMarshalRing(t *testing.T) {
281281
}
282282
}
283283

284+
// uncomment to test/benchmark custom json marshalling
285+
// func init() {
286+
// var c = jsoniter.Config{
287+
// EscapeHTML: true,
288+
// SortMapKeys: false,
289+
// ValidateJsonRawMessage: false,
290+
// MarshalFloatWith6Digits: true,
291+
// }.Froze()
292+
293+
// CustomJSONMarshaler = c
294+
// CustomJSONUnmarshaler = c
295+
// }
296+
284297
func BenchmarkFeatureMarshalJSON(b *testing.B) {
285298
data, err := ioutil.ReadFile("../encoding/mvt/testdata/16-17896-24449.json")
286299
if err != nil {
@@ -296,7 +309,7 @@ func BenchmarkFeatureMarshalJSON(b *testing.B) {
296309
b.ReportAllocs()
297310
b.ResetTimer()
298311
for i := 0; i < b.N; i++ {
299-
_, err := json.Marshal(tile)
312+
_, err := marshalJSON(tile)
300313
if err != nil {
301314
b.Fatalf("marshal error: %v", err)
302315
}
@@ -313,7 +326,7 @@ func BenchmarkFeatureUnmarshalJSON(b *testing.B) {
313326
b.ResetTimer()
314327
for i := 0; i < b.N; i++ {
315328
tile := map[string]*FeatureCollection{}
316-
err = json.Unmarshal(data, &tile)
329+
err = unmarshalJSON(data, &tile)
317330
if err != nil {
318331
b.Fatalf("could not unmarshal: %v", err)
319332
}

geojson/geometry.go

+23-29
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package geojson
22

33
import (
4-
"encoding/json"
54
"errors"
5+
66
"github.com/paulmach/orb"
77
)
88

@@ -85,14 +85,15 @@ func (g Geometry) MarshalJSON() ([]byte, error) {
8585
ng.Geometries = g.Geometries
8686
ng.Type = orb.Collection{}.GeoJSONType()
8787
}
88-
return json.Marshal(ng)
88+
89+
return marshalJSON(ng)
8990
}
9091

9192
// UnmarshalGeometry decodes the data into a GeoJSON feature.
9293
// Alternately one can call json.Unmarshal(g) directly for the same result.
9394
func UnmarshalGeometry(data []byte) (*Geometry, error) {
9495
g := &Geometry{}
95-
err := json.Unmarshal(data, g)
96+
err := unmarshalJSON(data, g)
9697
if err != nil {
9798
return nil, err
9899
}
@@ -103,35 +104,35 @@ func UnmarshalGeometry(data []byte) (*Geometry, error) {
103104
// UnmarshalJSON will unmarshal the correct geometry from the json structure.
104105
func (g *Geometry) UnmarshalJSON(data []byte) error {
105106
jg := &jsonGeometry{}
106-
err := json.Unmarshal(data, jg)
107+
err := unmarshalJSON(data, jg)
107108
if err != nil {
108109
return err
109110
}
110111

111112
switch jg.Type {
112113
case "Point":
113114
p := orb.Point{}
114-
err = json.Unmarshal(jg.Coordinates, &p)
115+
err = unmarshalJSON(jg.Coordinates, &p)
115116
g.Coordinates = p
116117
case "MultiPoint":
117118
mp := orb.MultiPoint{}
118-
err = json.Unmarshal(jg.Coordinates, &mp)
119+
err = unmarshalJSON(jg.Coordinates, &mp)
119120
g.Coordinates = mp
120121
case "LineString":
121122
ls := orb.LineString{}
122-
err = json.Unmarshal(jg.Coordinates, &ls)
123+
err = unmarshalJSON(jg.Coordinates, &ls)
123124
g.Coordinates = ls
124125
case "MultiLineString":
125126
mls := orb.MultiLineString{}
126-
err = json.Unmarshal(jg.Coordinates, &mls)
127+
err = unmarshalJSON(jg.Coordinates, &mls)
127128
g.Coordinates = mls
128129
case "Polygon":
129130
p := orb.Polygon{}
130-
err = json.Unmarshal(jg.Coordinates, &p)
131+
err = unmarshalJSON(jg.Coordinates, &p)
131132
g.Coordinates = p
132133
case "MultiPolygon":
133134
mp := orb.MultiPolygon{}
134-
err = json.Unmarshal(jg.Coordinates, &mp)
135+
err = unmarshalJSON(jg.Coordinates, &mp)
135136
g.Coordinates = mp
136137
case "GeometryCollection":
137138
g.Geometries = jg.Geometries
@@ -154,13 +155,13 @@ func (p Point) Geometry() orb.Geometry {
154155

155156
// MarshalJSON will convert the Point into a GeoJSON Point geometry.
156157
func (p Point) MarshalJSON() ([]byte, error) {
157-
return json.Marshal(Geometry{Coordinates: orb.Point(p)})
158+
return marshalJSON(Geometry{Coordinates: orb.Point(p)})
158159
}
159160

160161
// UnmarshalJSON will unmarshal the GeoJSON Point geometry.
161162
func (p *Point) UnmarshalJSON(data []byte) error {
162163
g := &Geometry{}
163-
err := json.Unmarshal(data, &g)
164+
err := unmarshalJSON(data, &g)
164165
if err != nil {
165166
return err
166167
}
@@ -184,13 +185,13 @@ func (mp MultiPoint) Geometry() orb.Geometry {
184185

185186
// MarshalJSON will convert the MultiPoint into a GeoJSON MultiPoint geometry.
186187
func (mp MultiPoint) MarshalJSON() ([]byte, error) {
187-
return json.Marshal(Geometry{Coordinates: orb.MultiPoint(mp)})
188+
return marshalJSON(Geometry{Coordinates: orb.MultiPoint(mp)})
188189
}
189190

190191
// UnmarshalJSON will unmarshal the GeoJSON MultiPoint geometry.
191192
func (mp *MultiPoint) UnmarshalJSON(data []byte) error {
192193
g := &Geometry{}
193-
err := json.Unmarshal(data, &g)
194+
err := unmarshalJSON(data, &g)
194195
if err != nil {
195196
return err
196197
}
@@ -214,13 +215,13 @@ func (ls LineString) Geometry() orb.Geometry {
214215

215216
// MarshalJSON will convert the LineString into a GeoJSON LineString geometry.
216217
func (ls LineString) MarshalJSON() ([]byte, error) {
217-
return json.Marshal(Geometry{Coordinates: orb.LineString(ls)})
218+
return marshalJSON(Geometry{Coordinates: orb.LineString(ls)})
218219
}
219220

220221
// UnmarshalJSON will unmarshal the GeoJSON MultiPoint geometry.
221222
func (ls *LineString) UnmarshalJSON(data []byte) error {
222223
g := &Geometry{}
223-
err := json.Unmarshal(data, &g)
224+
err := unmarshalJSON(data, &g)
224225
if err != nil {
225226
return err
226227
}
@@ -244,13 +245,13 @@ func (mls MultiLineString) Geometry() orb.Geometry {
244245

245246
// MarshalJSON will convert the MultiLineString into a GeoJSON MultiLineString geometry.
246247
func (mls MultiLineString) MarshalJSON() ([]byte, error) {
247-
return json.Marshal(Geometry{Coordinates: orb.MultiLineString(mls)})
248+
return marshalJSON(Geometry{Coordinates: orb.MultiLineString(mls)})
248249
}
249250

250251
// UnmarshalJSON will unmarshal the GeoJSON MultiPoint geometry.
251252
func (mls *MultiLineString) UnmarshalJSON(data []byte) error {
252253
g := &Geometry{}
253-
err := json.Unmarshal(data, &g)
254+
err := unmarshalJSON(data, &g)
254255
if err != nil {
255256
return err
256257
}
@@ -274,13 +275,13 @@ func (p Polygon) Geometry() orb.Geometry {
274275

275276
// MarshalJSON will convert the Polygon into a GeoJSON Polygon geometry.
276277
func (p Polygon) MarshalJSON() ([]byte, error) {
277-
return json.Marshal(Geometry{Coordinates: orb.Polygon(p)})
278+
return marshalJSON(Geometry{Coordinates: orb.Polygon(p)})
278279
}
279280

280281
// UnmarshalJSON will unmarshal the GeoJSON Polygon geometry.
281282
func (p *Polygon) UnmarshalJSON(data []byte) error {
282283
g := &Geometry{}
283-
err := json.Unmarshal(data, &g)
284+
err := unmarshalJSON(data, &g)
284285
if err != nil {
285286
return err
286287
}
@@ -304,13 +305,13 @@ func (mp MultiPolygon) Geometry() orb.Geometry {
304305

305306
// MarshalJSON will convert the MultiPolygon into a GeoJSON MultiPolygon geometry.
306307
func (mp MultiPolygon) MarshalJSON() ([]byte, error) {
307-
return json.Marshal(Geometry{Coordinates: orb.MultiPolygon(mp)})
308+
return marshalJSON(Geometry{Coordinates: orb.MultiPolygon(mp)})
308309
}
309310

310311
// UnmarshalJSON will unmarshal the GeoJSON MultiPolygon geometry.
311312
func (mp *MultiPolygon) UnmarshalJSON(data []byte) error {
312313
g := &Geometry{}
313-
err := json.Unmarshal(data, &g)
314+
err := unmarshalJSON(data, &g)
314315
if err != nil {
315316
return err
316317
}
@@ -335,10 +336,3 @@ type jsonGeometryMarshall struct {
335336
Coordinates orb.Geometry `json:"coordinates,omitempty"`
336337
Geometries []*Geometry `json:"geometries,omitempty"`
337338
}
338-
339-
type nocopyRawMessage []byte
340-
341-
func (m *nocopyRawMessage) UnmarshalJSON(data []byte) error {
342-
*m = data
343-
return nil
344-
}

0 commit comments

Comments
 (0)