From 711c99136a6bae2f3a6e34af4fa7e2b2f1fa92ff Mon Sep 17 00:00:00 2001 From: Aleksei Gurianov Date: Mon, 28 Oct 2024 22:21:06 +0300 Subject: [PATCH] feat: remove null properties encoder and decoder Signed-off-by: Aleksei Gurianov --- README.md | 87 +++++++++++++++++++++++++++++++++++++- src/gleojson.gleam | 24 ++--------- test/examples/decode.gleam | 80 +++++++++++++++++++++++++++++++++++ test/examples/encode.gleam | 2 +- test/gleojson_test.gleam | 36 +++++++++------- 5 files changed, 191 insertions(+), 38 deletions(-) create mode 100644 test/examples/decode.gleam diff --git a/README.md b/README.md index 5c78254..2782b9e 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,96 @@ pub fn main() { ) // Encode the Feature to GeoJSON |> gleojson.GeoFeature - |> gleojson.encode_geojson(gleojson.properties_null_encoder) + |> gleojson.encode_geojson(fn(_) { json.null() }) |> json.to_string } ``` +Decoding GeoJSON objects is also straightforward: + +```gleam:./test/examples/decode.gleam +import decode/zero +import gleam/bool +import gleam/float +import gleam/int +import gleam/io +import gleam/json +import gleam/option +import gleojson + +// Define custom properties type for your features +pub type ParkProperties { + ParkProperties( + name: String, + area_sq_km: Float, + year_established: Int, + is_protected: Bool, + ) +} + +// Define decoder for your custom properties +fn park_properties_decoder() { + use name <- zero.field("name", zero.string) + use area_sq_km <- zero.field("area_sq_km", zero.float) + use year_established <- zero.field("year_established", zero.int) + use is_protected <- zero.field("is_protected", zero.bool) + ParkProperties(name:, area_sq_km:, year_established:, is_protected:) + |> zero.success +} + +pub fn main() { + // Example GeoJSON string representing a national park + let json_string = + "{ + \"type\": \"Feature\", + \"geometry\": { + \"type\": \"Point\", + \"coordinates\": [-119.5383, 37.8651] + }, + \"properties\": { + \"name\": \"Yosemite National Park\", + \"area_sq_km\": 3029.87, + \"year_established\": 1890, + \"is_protected\": true + }, + \"id\": \"yosemite\" + }" + + // Decode the JSON string into a GeoJSON object + let decoded = + json.decode( + from: json_string, + using: zero.run(_, gleojson.geojson_decoder(park_properties_decoder())), + ) + + // Handle the decoded result + case decoded { + Ok(geojson) -> { + case geojson { + gleojson.GeoFeature(feature) -> { + case feature.properties { + option.Some(ParkProperties(name, area, year, is_protected)) -> { + io.println( + "Decoded " <> name <> ", established in " <> int.to_string(year), + ) + io.println( + "Area: " + <> float.to_string(area) + <> " sq km, Protected: " + <> bool.to_string(is_protected), + ) + } + option.None -> io.println("Feature has no properties") + } + } + _ -> io.println("Decoded a different type of GeoJSON object") + } + } + Error(_error) -> io.println("Failed to decode") + } +} +``` + For more advanced usage, including custom properties and decoding, see the [documentation](https://hexdocs.pm/gleojson). ## Development diff --git a/src/gleojson.gleam b/src/gleojson.gleam index 4b0f0e3..48b51a3 100644 --- a/src/gleojson.gleam +++ b/src/gleojson.gleam @@ -329,9 +329,7 @@ fn featurecollection_decoder(properties_decoder: zero.Decoder(properties)) { /// let decoded = /// json.decode( /// from: json_string, -/// using: fn(dynamic_value) { -/// zero.run(dynamic_value, gleojson.geojson_decoder(custom_properties_decoder())) -/// } +/// using: zero.run(_, gleojson.geojson_decoder(custom_properties_decoder())) /// ) /// /// case decoded { @@ -350,8 +348,8 @@ fn featurecollection_decoder(properties_decoder: zero.Decoder(properties)) { /// } /// ``` /// -/// Note: This function expects a valid GeoJSON structure. Invalid or incomplete -/// GeoJSON data will result in a decode error. +/// Note: This function expects a valid GeoJSON structure. +/// Invalid or incomplete GeoJSON data will result in a decode error. pub fn geojson_decoder(properties_decoder: zero.Decoder(properties)) { use type_str <- zero.then(type_decoder()) case type_str { @@ -363,22 +361,6 @@ pub fn geojson_decoder(properties_decoder: zero.Decoder(properties)) { } } -/// Encodes null properties for Features and FeatureCollections. -/// -/// This is a utility function that can be used as the `properties_encoder` -/// argument for `encode_geojson` when you don't need to encode any properties. -pub fn properties_null_encoder(_props) { - json.null() -} - -/// Decodes null properties for Features and FeatureCollections. -/// -/// This is a utility function that can be used as the `properties_decoder` -/// argument for `geojson_decoder` when you don't need to decode any properties. -pub fn properties_null_decoder() { - zero.success(Ok(Nil)) -} - /// Creates a 2D Position object from longitude and latitude values. /// /// This function is a convenience helper for creating a Position object diff --git a/test/examples/decode.gleam b/test/examples/decode.gleam new file mode 100644 index 0000000..975169a --- /dev/null +++ b/test/examples/decode.gleam @@ -0,0 +1,80 @@ +import decode/zero +import gleam/bool +import gleam/float +import gleam/int +import gleam/io +import gleam/json +import gleam/option +import gleojson + +// Define custom properties type for your features +pub type ParkProperties { + ParkProperties( + name: String, + area_sq_km: Float, + year_established: Int, + is_protected: Bool, + ) +} + +// Define decoder for your custom properties +fn park_properties_decoder() { + use name <- zero.field("name", zero.string) + use area_sq_km <- zero.field("area_sq_km", zero.float) + use year_established <- zero.field("year_established", zero.int) + use is_protected <- zero.field("is_protected", zero.bool) + ParkProperties(name:, area_sq_km:, year_established:, is_protected:) + |> zero.success +} + +pub fn main() { + // Example GeoJSON string representing a national park + let json_string = + "{ + \"type\": \"Feature\", + \"geometry\": { + \"type\": \"Point\", + \"coordinates\": [-119.5383, 37.8651] + }, + \"properties\": { + \"name\": \"Yosemite National Park\", + \"area_sq_km\": 3029.87, + \"year_established\": 1890, + \"is_protected\": true + }, + \"id\": \"yosemite\" + }" + + // Decode the JSON string into a GeoJSON object + let decoded = + json.decode( + from: json_string, + using: zero.run(_, gleojson.geojson_decoder(park_properties_decoder())), + ) + + // Handle the decoded result + case decoded { + Ok(geojson) -> { + case geojson { + gleojson.GeoFeature(feature) -> { + case feature.properties { + option.Some(ParkProperties(name, area, year, is_protected)) -> { + io.println( + "Decoded " <> name <> ", established in " <> int.to_string(year), + ) + io.println( + "Area: " + <> float.to_string(area) + <> " sq km, Protected: " + <> bool.to_string(is_protected), + ) + } + option.None -> io.println("Feature has no properties") + } + } + _ -> io.println("Decoded a different type of GeoJSON object") + } + } + Error(_error) -> io.println("Failed to decode") + } +} diff --git a/test/examples/encode.gleam b/test/examples/encode.gleam index a334981..5c0a59e 100644 --- a/test/examples/encode.gleam +++ b/test/examples/encode.gleam @@ -13,6 +13,6 @@ pub fn main() { ) // Encode the Feature to GeoJSON |> gleojson.GeoFeature - |> gleojson.encode_geojson(gleojson.properties_null_encoder) + |> gleojson.encode_geojson(fn(_) { json.null() }) |> json.to_string } diff --git a/test/gleojson_test.gleam b/test/gleojson_test.gleam index b284ff6..17c7d0c 100644 --- a/test/gleojson_test.gleam +++ b/test/gleojson_test.gleam @@ -1,5 +1,6 @@ import birdie import decode/zero +import examples/decode import examples/encode import gleam/dynamic import gleam/json @@ -119,9 +120,9 @@ fn assert_encode_decode( birdie.snap(encoded, name) - json.decode(from: encoded, using: fn(dynamic_value) { - zero.run(dynamic_value, gleojson.geojson_decoder(properties_decoder)) - }) + json.decode(from: encoded, using: zero.run(_, gleojson.geojson_decoder( + properties_decoder, + ))) |> should.be_ok |> should.equal(geojson) } @@ -134,8 +135,8 @@ pub fn point_encode_decode_test() { assert_encode_decode( geojson, - gleojson.properties_null_encoder, - gleojson.properties_null_decoder(), + fn(_) { json.null() }, + zero.success(Nil), "point_encode_decode", ) } @@ -151,8 +152,8 @@ pub fn multipoint_encode_decode_test() { assert_encode_decode( geojson, - gleojson.properties_null_encoder, - gleojson.properties_null_decoder(), + fn(_) { json.null() }, + zero.success(Nil), "multipoint_encode_decode", ) } @@ -168,8 +169,8 @@ pub fn linestring_encode_decode_test() { assert_encode_decode( geojson, - gleojson.properties_null_encoder, - gleojson.properties_null_decoder(), + fn(_) { json.null() }, + zero.success(Nil), "linestring_encode_decode", ) } @@ -189,8 +190,8 @@ pub fn polygon_encode_decode_test() { assert_encode_decode( geojson, - gleojson.properties_null_encoder, - gleojson.properties_null_decoder(), + fn(_) { json.null() }, + zero.success(Nil), "polygon_encode_decode", ) } @@ -220,8 +221,8 @@ pub fn multipolygon_encode_decode_test() { assert_encode_decode( geojson, - gleojson.properties_null_encoder, - gleojson.properties_null_decoder(), + fn(_) { json.null() }, + zero.success(Nil), "multipolygon_encode_decode", ) } @@ -240,8 +241,8 @@ pub fn geometrycollection_encode_decode_test() { assert_encode_decode( geojson, - gleojson.properties_null_encoder, - gleojson.properties_null_decoder(), + fn(_) { json.null() }, + zero.success(Nil), "geometrycollection_encode_decode", ) } @@ -344,3 +345,8 @@ pub fn example_test() { |> dynamic.classify() |> should.equal("String") } + +pub fn example_decode_test() { + decode.main() + |> should.equal(Nil) +}