Skip to content

Instantly share code, notes, and snippets.

@MangelMaxime
Created August 20, 2020 12:57
Show Gist options
  • Save MangelMaxime/23fa8091574be9346b852d9027fbb76a to your computer and use it in GitHub Desktop.
Save MangelMaxime/23fa8091574be9346b852d9027fbb76a to your computer and use it in GitHub Desktop.
Created with Fable REPL
open Thoth.Json
open Fable.Core
open Fable.Core.JS
open Fable.Core.JsInterop
// Code copied from https://github.com/MangelMaxime/Fable.Geojson/blob/master/src/Geojson.fs
// REPL include Geojson library so I copy/paste the code in order to have equivalent
module GeoJson =
type [<StringEnum>] [<RequireQualifiedAccess>] GeoJsonGeometryTypes =
| [<CompiledName "Point">] Point
| [<CompiledName "LineString">] LineString
| [<CompiledName "MultiPoint">] MultiPoint
| [<CompiledName "Polygon">] Polygon
| [<CompiledName "MultiLineString">] MultiLineString
| [<CompiledName "MultiPolygon">] MultiPolygon
| [<CompiledName "GeometryCollection">] GeometryCollection
type GeoJsonTypes =
U3<string, string, GeoJsonGeometryTypes>
type Position =
ResizeArray<float>
type BBox =
U2<float * float * float * float, float * float * float * float * float * float>
/// The base GeoJSON object.
/// https://tools.ietf.org/html/rfc7946#section-3
/// The GeoJSON specification also allows foreign members
/// (https://tools.ietf.org/html/rfc7946#section-6.1)
/// Developers should use "&" type in TypeScript or extend the interface
/// to add these foreign members.
type [<AllowNullLiteral>] GeoJsonObject =
/// Specifies the type of GeoJSON object.
abstract ``type``: GeoJsonTypes with get, set
/// Bounding box of the coordinate range of the object's Geometries, Features, or Feature Collections.
/// https://tools.ietf.org/html/rfc7946#section-5
abstract bbox: BBox option with get, set
/// A geometry object.
/// https://tools.ietf.org/html/rfc7946#section-3
type [<AllowNullLiteral>] GeometryObject =
inherit GeoJsonObject
abstract ``type``: GeoJsonGeometryTypes with get, set
/// Point geometry object.
/// https://tools.ietf.org/html/rfc7946#section-3.1.2
type [<AllowNullLiteral>] Point =
inherit GeometryObject
abstract ``type``: string with get, set
abstract coordinates: Position with get, set
/// A feature object which contains a geometry and associated properties.
/// https://tools.ietf.org/html/rfc7946#section-3.2
type [<AllowNullLiteral>] Feature<'G, 'P> =
inherit GeoJsonObject
abstract ``type``: string with get, set
/// The feature's geometry
abstract geometry: 'G with get, set
/// A value that uniquely identifies this feature in a
/// https://tools.ietf.org/html/rfc7946#section-3.2.
abstract id: U2<string, float> option with get, set
/// Properties associated with this feature.
abstract properties: 'P with get, set
// In your project you can use type abbreviation to attach your encoder/decoder to the types
type GeoJson.GeoJsonGeometryTypes with
static member Decoder =
Decode.string
|> Decode.andThen (fun value ->
match value with
| "Point" -> Decode.succeed GeoJson.GeoJsonGeometryTypes.Point
| "LineString" -> Decode.succeed GeoJson.GeoJsonGeometryTypes.LineString
| "MultiPoint" -> Decode.succeed GeoJson.GeoJsonGeometryTypes.MultiPoint
| "Polygon" -> Decode.succeed GeoJson.GeoJsonGeometryTypes.Polygon
| "MultiLineString" -> Decode.succeed GeoJson.GeoJsonGeometryTypes.MultiLineString
| "MultiPolygon" -> Decode.succeed GeoJson.GeoJsonGeometryTypes.MultiPolygon
| "GeometryCollection" -> Decode.succeed GeoJson.GeoJsonGeometryTypes.GeometryCollection
| invalid ->
"`" + invalid + "` is not a valid value for GeoJson.GeoJsonGeometryTypes"
|> Decode.fail
)
// Homwever, with all the inerithance in the library it seems like the compiler is confused
// Example:
// type GeoJson.GeoJsonObject with
// static member Decoder =
// Decode.object (fun get ->
// // Use Fable interop to create the object instance
// // You can use createEmpty, jsOption, etc.
// let res = createEmpty<GeoJson.GeoJsonObject>
// res.``type`` <- get.Required.Field "type" GeoJsonTypes.Decoder
// res.bbox <- get.Optional.Field "bbox" BBox.Decoder
// // I am not sure which version will work in your case
// // I think get.Optional.Field should do the job but I mention this one just in case
// // res.bbox <- get.Required.Field "bbox" (Decode.option BBox.Decoder)
// )
// type GeoJson.GeometryObject with
// static member Decoder =
// Decode.object (fun get ->
// let res = createEmpty<GeoJson.GeometryObject>
// res.``type`` <- get.Required.Field "type" GeoJson.GeoJsonGeometryTypes.Decoder
// res.bbox <- get.Optional.Field "bbox" BBox.Decoder
// )
// When calling GeoJson.GeometryObject.Decoder F# compiler don't seems to be able to choose between
// GeoJson.GeoJsonObject.Decoder and GeoJson.GeometryObject.Decoder
// Because GeoJson.GeometryObject inherit from GeoJson.GeoJsonObject
// You can also just use module with function to mimic the type structure (for intellisense purpose)
// Or just use function:
// let GeoJsonTypesGeoJsonTypes : Decoder<GeoJson.GeoJsonTypes> = ...
module GeoJsonTypes =
let Decoder : Decoder<GeoJson.GeoJsonTypes> =
// You can use oneOf in order to support U2, U3, ... types
// If you know you want to use only one of the version you don't need to code all of them
Decode.oneOf [
GeoJson.GeoJsonGeometryTypes.Decoder |> Decode.map U3.Case3
Decode.string |> Decode.map U3.Case1
]
module Position =
let Decoder : Decoder<GeoJson.Position> =
Decode.array Decode.float
|> Decode.map ResizeArray
module BBox =
let Decoder : Decoder<GeoJson.BBox> =
Decode.oneOf [
Decode.tuple4
Decode.float
Decode.float
Decode.float
Decode.float
|> Decode.map U2.Case1
Decode.tuple6
Decode.float
Decode.float
Decode.float
Decode.float
Decode.float
Decode.float
|> Decode.map U2.Case2
]
module GeoJsonObject =
let Decoder : Decoder<GeoJson.GeoJsonObject> =
Decode.object (fun get ->
// Use Fable interop to create the object instance
// You can use createEmpty, jsOption, etc.
let res = createEmpty<GeoJson.GeoJsonObject>
res.``type`` <- get.Required.Field "type" GeoJsonTypes.Decoder
res.bbox <- get.Optional.Field "bbox" BBox.Decoder
res
// I am not sure which version will work in your case
// I think get.Optional.Field should do the job but I mention this one just in case
// res.bbox <- get.Required.Field "bbox" (Decode.option BBox.Decoder)
)
module GeometryObject =
let Decoder : Decoder<GeoJson.GeometryObject> =
Decode.object (fun get ->
let res = createEmpty<GeoJson.GeometryObject>
res.``type`` <- get.Required.Field "type" GeoJson.GeoJsonGeometryTypes.Decoder
res.bbox <- get.Optional.Field "bbox" BBox.Decoder
res
)
module Point =
let Decoder : Decoder<GeoJson.Point> =
Decode.object (fun get ->
let res = createEmpty<GeoJson.Point>
res.``type`` <- get.Required.Field "type" Decode.string
res.bbox <- get.Optional.Field "bbox" BBox.Decoder
res.coordinates <- get.Required.Field "coordinates" Position.Decoder
res
)
module Feature =
let Decoder<'G, 'P> (geometryDecoder : Decoder<'G>) (propertiesDecoder : Decoder<'P>) : Decoder<GeoJson.Feature<'G, 'P>> =
Decode.object (fun get ->
let idDecoder =
Decode.oneOf [
Decode.string |> Decode.map U2.Case1
Decode.float |> Decode.map U2.Case2
]
let res = createEmpty<GeoJson.Feature<'G, 'P>>
res.``type`` <- get.Required.Field "type" Decode.string
res.geometry <- get.Required.Field "geometry" geometryDecoder
res.id <- get.Optional.Field "id" idDecoder
res.bbox <- get.Optional.Field "bbox" BBox.Decoder
res.properties <- get.Required.Field "properties" propertiesDecoder
res
)
// This JSON has been copied from https://geojson.org/
// So I guess it should be valid
let json =
"""
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands"
}
}
"""
// You can use anonymous record if you don't want to create a real type for some of the values
let myPropertiesDecoder =
Decode.object (fun get ->
{|
name = get.Required.Field "name" Decode.string
|}
)
match Decode.fromString (Feature.Decoder Point.Decoder myPropertiesDecoder) json with
| Ok value ->
printfn "id: %A" value.id
JS.console.log("geometry: ", value.geometry)
printfn "geometry.type: %A" value.geometry.``type``
printfn "geometry.coordinates: %A" value.geometry.coordinates
printfn "properties: %A" value.properties
| Error err ->
JS.console.log err
<!doctype html>
<html>
<head>
<title>Fable + Recharts</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="container1"></div>
<script src="bundle.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment