Skip to content

Commit 06e46f3

Browse files
authoredSep 28, 2021
Create GeoJSON serializer and parser (rstudio#830)
* Create geojson serializer * Create geojson serializer tests * update serializers doc * add Josiah as contributor * add serializer_geojson to NAMESPACE * add geojsonsf to suggests * add geojson parser along tests * make requested adjustments to serializer and serializer test * add sf under suggest for tests * include namespace on geojson_sf call in tests Pass a non valid geojson value to the serializer. * Address requested changes in rstudio#830. - Update namespace w/ devtools::document() - Update news under "New features" - Move check for `geojsonsf` package to top of function - Add `application/vdn.geo+json` as valid content type Co-authored-by: josiahparry <josiah.parry@gmail.com>
1 parent 89b9730 commit 06e46f3

9 files changed

+112
-2
lines changed
 

‎DESCRIPTION

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ Suggests:
4949
future,
5050
rstudioapi,
5151
spelling,
52-
mockery (>= 0.4.2)
52+
mockery (>= 0.4.2),
53+
geojsonsf,
54+
sf
5355
RoxygenNote: 7.1.1
5456
Collate:
5557
'async.R'

‎NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export(options_plumber)
2828
export(parser_csv)
2929
export(parser_feather)
3030
export(parser_form)
31+
export(parser_geojson)
3132
export(parser_json)
3233
export(parser_multi)
3334
export(parser_none)
@@ -76,6 +77,7 @@ export(serializer_csv)
7677
export(serializer_device)
7778
export(serializer_feather)
7879
export(serializer_format)
80+
export(serializer_geojson)
7981
export(serializer_headers)
8082
export(serializer_html)
8183
export(serializer_htmlwidget)

‎NEWS.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## Breaking changes
44

55
## New features
6+
7+
* Introduces new Geojson serializer and parser. Geojson objects are parsed into `sf` objects and `sf` or `sfc` objects will be serialized into geojson. (@josiahparry, #830)
8+
69
## Bug fixes
710

811
* OpenAPI response type detection had a scoping issue. Use serializer defined `Content-Type` header instead. (@meztez, #789)

‎R/parse-body.R

+11
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,16 @@ parser_json <- function(...) {
386386
})
387387
}
388388

389+
#' @describeIn parsers GeoJSON parser. See [geojsonsf::geojson_sf()] for more details.
390+
#' @export
391+
parser_geojson <- function(...) {
392+
if (!requireNamespace("geojsonsf", quietly = TRUE)) {
393+
stop("`geojsonsf` must be installed for `parser_geojson` to work")
394+
}
395+
parser_text(function(val) {
396+
geojsonsf::geojson_sf(val, ...)
397+
})
398+
}
389399

390400
#' @describeIn parsers Helper parser to parse plain text
391401
#' @param parse_fn function to further decode a text string into an object
@@ -564,6 +574,7 @@ register_parsers_onLoad <- function() {
564574
# yaml types: https://stackoverflow.com/a/38000954/591574
565575
register_parser("yaml", parser_yaml, fixed = c("text/vnd.yaml", "application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"))
566576
register_parser("none", parser_none, regex = "*")
577+
register_parser("geojson", parser_geojson, fixed = c("application/geo+json", "application/vdn.geo+json"))
567578

568579
parser_all <- function() {
569580
stop("This function should never be called. It should be handled by `make_parser('all')`")

‎R/serializer.R

+13
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,18 @@ serializer_unboxed_json <- function(auto_unbox = TRUE, ..., type = "application/
231231
serializer_json(auto_unbox = auto_unbox, ..., type = type)
232232
}
233233

234+
#' @describeIn serializers GeoJSON serializer. See also [geojsonsf::sf_geojson()] and [[geojsonsf::sfc_geojson()]].
235+
#' @export
236+
serializer_geojson <- function(..., type = "application/geo+json") {
237+
if (!requireNamespace("geojsonsf", quietly = TRUE)) {
238+
stop("`geojsonsf` must be installed for `serializer_geojson` to work")
239+
}
240+
serializer_content_type(type, function(val) {
241+
if (inherits(val, "sfc")) return(geojsonsf::sfc_geojson(val, ...))
242+
if (inherits(val, "sf")) return(geojsonsf::sf_geojson(val, ...))
243+
stop("Did not receive an `sf` or `sfc` object. ")
244+
})
245+
}
234246

235247

236248

@@ -603,6 +615,7 @@ add_serializers_onLoad <- function() {
603615
register_serializer("tsv", serializer_tsv)
604616
register_serializer("feather", serializer_feather)
605617
register_serializer("yaml", serializer_yaml)
618+
register_serializer("geojson", serializer_geojson)
606619

607620
# text
608621
register_serializer("text", serializer_text)

‎man/parsers.Rd

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎man/serializers.Rd

+6-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/testthat/test-parse-body.R

+34
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,40 @@ test_that("Test feather parser", {
109109
expect_equal(parsed, r_object)
110110
})
111111

112+
test_that("Test geojson parser", {
113+
skip_if_not_installed("geojsonsf")
114+
skip_if_not_installed("sf")
115+
116+
# Test sf object w/ fields
117+
geojson <- '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"a":3},"geometry":{"type":"Point","coordinates":[1,2]}},{"type":"Feature","properties":{"a":4},"geometry":{"type":"Point","coordinates":[3,4]}}]}'
118+
parsed <- parse_body(geojson, "application/geo+json", make_parser("geojson"))
119+
expect_equal(parsed, geojsonsf::geojson_sf(geojson))
120+
121+
# Test sfc
122+
geojson <- '[
123+
{ "type":"Point","coordinates":[0,0]},
124+
{"type":"LineString","coordinates":[[0,0],[1,1]]}
125+
]'
126+
parsed <- parse_body(geojson, "application/geo+json", make_parser("geojson"))
127+
expect_equal(parsed, geojsonsf::geojson_sf(geojson))
128+
129+
# Test simple sf object
130+
geojson <- '{ "type" : "Point", "coordinates" : [0, 0] }'
131+
parsed <- parse_body(geojson, "application/geo+json", make_parser("geojson"))
132+
expect_equal(parsed, geojsonsf::geojson_sf(geojson))
133+
134+
# Test geojson file
135+
tmp <- tempfile()
136+
on.exit({
137+
file.remove(tmp)
138+
}, add = TRUE)
139+
140+
writeLines(geojson, tmp)
141+
val <- readBin(tmp, "raw", 1000)
142+
parsed <- parse_body(val, "application/geo+json", make_parser("geojson"))
143+
expect_equal(parsed, geojsonsf::geojson_sf(geojson))
144+
145+
})
112146

113147
test_that("Test multipart output is reduced for argument matching", {
114148
bin_file <- test_path("files/multipart-file-names.bin")
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
test_that("GeoJSON serializes properly", {
2+
skip_if_not_installed("geojsonsf")
3+
skip_if_not_installed("sf")
4+
5+
# Objects taken from ?st_sf() examples.
6+
sfc <- sf::st_sfc(sf::st_point(1:2), sf::st_point(3:4))
7+
sf <- sf::st_sf(a = 3:4, sfc)
8+
9+
# Test sfc
10+
val <- serializer_geojson()(sfc, data.frame(), PlumberResponse$new(), stop)
11+
expect_equal(val$status, 200L)
12+
expect_equal(val$headers$`Content-Type`, "application/geo+json")
13+
expect_equal(val$body, geojsonsf::sfc_geojson(sfc))
14+
15+
# Test sf
16+
val <- serializer_geojson()(sf, data.frame(), PlumberResponse$new(), stop)
17+
expect_equal(val$status, 200L)
18+
expect_equal(val$headers$`Content-Type`, "application/geo+json")
19+
expect_equal(val$body, geojsonsf::sf_geojson(sf))
20+
21+
})
22+
23+
test_that("Errors call error handler", {
24+
skip_if_not_installed("geojsonsf")
25+
skip_if_not_installed("sf")
26+
27+
errors <- 0
28+
errHandler <- function(req, res, err){
29+
errors <<- errors + 1
30+
}
31+
32+
expect_equal(errors, 0)
33+
serializer_geojson()(parse(text="h$534i} {!"), data.frame(), PlumberResponse$new(), errorHandler = errHandler)
34+
expect_equal(errors, 1)
35+
})

0 commit comments

Comments
 (0)
Please sign in to comment.