From df773a94e59610d49612310f34e1312152ea3eaf Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Wed, 21 Sep 2022 10:42:47 +0200 Subject: [PATCH] Add missing tests for serialization and fix codecs to pass them --- .../scala/com/free2move/geoscala/circe.scala | 31 ++-- .../CirceDecodingAndEncodingTests.scala | 50 +++++++ .../geoscala/CirceDecodingTests.scala | 86 ----------- .../free2move/geoscala/jsoniter_scala.scala | 141 +++++++++++++++--- .../geoscala/JsoniterScalaCodecTests.scala | 57 +++++++ .../geoscala/JsoniterScalaDecodingTests.scala | 87 ----------- 6 files changed, 248 insertions(+), 204 deletions(-) create mode 100644 circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala delete mode 100644 circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala create mode 100644 jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala delete mode 100644 jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala diff --git a/circe/src/main/scala/com/free2move/geoscala/circe.scala b/circe/src/main/scala/com/free2move/geoscala/circe.scala index a29ea67..f5c5d79 100644 --- a/circe/src/main/scala/com/free2move/geoscala/circe.scala +++ b/circe/src/main/scala/com/free2move/geoscala/circe.scala @@ -21,13 +21,14 @@ import io.circe._ import io.circe.syntax._ trait LowPriorityGeoJsonEncoders { - implicit val coordinateEncoder: Encoder[Coordinate] = Encoder.instance { coord => + implicit val coordinateEncoder: Encoder[Coordinate] = Encoder.instance { coord: Coordinate => Json.arr(Json.fromDoubleOrNull(coord.longitude), Json.fromDoubleOrNull(coord.latitude)) } - private def makeGeometryEncoder[C: Encoder, G <: Geometry](`type`: String, coords: G => C): Encoder[G] = Encoder.instance { geometry => - Json.obj("type" := `type`, "coordinates" := coords(geometry)) - } + private def makeGeometryEncoder[C: Encoder, G <: Geometry](`type`: String, coords: G => C): Encoder[G] = + Encoder.instance { geometry: G => + Json.obj("type" := `type`, "coordinates" := coords(geometry)) + } implicit val pointEncoder: Encoder[Point] = makeGeometryEncoder("Point", _.coordinates) @@ -53,18 +54,20 @@ trait GeoJsonEncoders extends LowPriorityGeoJsonEncoders { case mp: MultiPolygon => mp.asJson } - implicit def extendedFeatureEncoder[Properties: Encoder.AsObject]: Encoder[Feature[Properties]] = Encoder.instance { feature => - Json.obj("type" := "Feature", "properties" := feature.properties, "geometry" := feature.geometry) - } + implicit def extendedFeatureEncoder[Properties: Encoder]: Encoder[Feature[Properties]] = + Encoder.instance { feature: Feature[Properties] => + Json.obj("type" := "Feature", "properties" := feature.properties, "geometry" := feature.geometry) + } - implicit def extendedFeatureCollectionEncoder[Properties: Encoder.AsObject]: Encoder[FeatureCollection[Properties]] = Encoder.instance { featureCollection => - Json.obj("type" := "FeatureCollection", "features" := featureCollection.features) - } + implicit def extendedFeatureCollectionEncoder[Properties: Encoder]: Encoder[FeatureCollection[Properties]] = + Encoder.instance { featureCollection: FeatureCollection[Properties] => + Json.obj("type" := "FeatureCollection", "features" := featureCollection.features) + } - implicit def geojsonEncoder[Properties: Encoder.AsObject]: Encoder[GeoJson[Properties]] = Encoder.instance { - case fc @ FeatureCollection(_) => fc.asJson - case f @ Feature(_, _) => f.asJson - case geom: Geometry => (geom: Geometry).asJson + implicit def geojsonEncoder[Properties: Encoder]: Encoder[GeoJson[Properties]] = Encoder.instance { + case fc: FeatureCollection[Properties] => fc.asJson + case f: Feature[Properties] => f.asJson + case geom: Geometry => (geom: Geometry).asJson } } diff --git a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala new file mode 100644 index 0000000..fd3fcd6 --- /dev/null +++ b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala @@ -0,0 +1,50 @@ +/* + * Copyright 2019 GHM Mobile Development GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.free2move.geoscala + +import com.free2move.geoscala.circe._ +import io.circe._ +import io.circe.syntax._ +import org.scalatest.EitherValues +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class CirceDecodingAndEncodingTests extends AnyFlatSpec with Matchers with EitherValues { + "The circe decoders" should "parse and serialize simple 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + parser.decode[Point](json) shouldBe Right(obj) + parser.decode[Geometry](json) shouldBe Right(obj) + Printer.noSpaces.print(obj.asJson) shouldBe json + } + + it should "parse points with more dimensions as 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775,7.000,42.12345]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + parser.decode[Point](json) shouldBe Right(obj) + parser.decode[Geometry](json) shouldBe Right(obj) + } + + it should "parse and serialize FeatureCollection using Json encoder and decoder" in { + val json = + """{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":7},"geometry":{"type":"Point","coordinates":[12.3046875,51.8357775]}}]}""" + val obj = FeatureCollection(List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775))))) + parser.decode[FeatureCollection[Json]](json) shouldBe Right(obj) + parser.decode[GeoJson[Json]](json) shouldBe Right(obj) + Printer.noSpaces.print(obj.asJson) shouldBe json + } +} diff --git a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala deleted file mode 100644 index 439c891..0000000 --- a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2019 GHM Mobile Development GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.free2move.geoscala - -import com.free2move.geoscala.circe._ -import io.circe._ -import io.circe.syntax._ -import org.scalatest.EitherValues -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class CirceDecodingTests extends AnyFlatSpec with Matchers with EitherValues { - "The circe decoders" should "handle simple 2D points" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - }""" - parser.decode[Point](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - parser.decode[Geometry](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - } - - it should "handle points with more dimensions" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775, - 7.000, - 42.12345 - ] - }""" - parser.decode[Point](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - parser.decode[Geometry](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - } - - it should "handle FeatureCollection without Properties as pure JSON correctly" in { - val json = - """{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "id": 7 - }, - "geometry": { - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - } - } - ] - }""" - parser.decode[FeatureCollection[Json]](json) shouldBe Right( - FeatureCollection( - List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - ) - parser.decode[GeoJson[Json]](json) shouldBe Right( - FeatureCollection( - List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - ) - } -} diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index d32144d..b18adf7 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -18,7 +18,7 @@ package com.free2move.geoscala import com.github.plokhotnyuk.jsoniter_scala.core._ import com.github.plokhotnyuk.jsoniter_scala.macros._ -import scala.annotation.nowarn +import scala.collection.mutable.ListBuffer object jsoniter_scala { // Uncomment for printing of codecs generated by macros @@ -33,7 +33,7 @@ object jsoniter_scala { val lat = in.readDouble() while (in.isNextToken(',')) in.skip() if (!in.isCurrentToken(']')) in.arrayEndOrCommaError() - Coordinate(lon, lat) + new Coordinate(lon, lat) } else in.readNullOrTokenError(default, '[') override def encodeValue(x: Coordinate, out: JsonWriter): Unit = { @@ -70,9 +70,10 @@ object jsoniter_scala { implicit val multiPolygonCodec: JsonValueCodec[MultiPolygon] = makeGeometryCodec("MultiPolygon", _.coordinates, MultiPolygon.apply) - private def makeGeometryCodec[C: JsonValueCodec, G <: Geometry](tpe: String, coords: G => C, geom: C => G): JsonValueCodec[G] = + private[this] def makeGeometryCodec[C, G <: Geometry](tpe: String, coords: G => C, geom: C => G) + (implicit coordinatesCodec: JsonValueCodec[C]): JsonValueCodec[G] = new JsonValueCodec[G] { - private val coordinatesCodec: JsonValueCodec[C] = implicitly[JsonValueCodec[C]] + override val nullValue: G = null.asInstanceOf[G] override def decodeValue(in: JsonReader, default: G): G = if (in.isNextToken('{')) { @@ -84,7 +85,9 @@ object jsoniter_scala { if (in.isCharBufEqualsTo(len, "type")) { if ((mask & 0x1) != 0) mask ^= 0x1 else in.duplicatedKeyError(len) - if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), tpe)) in.discriminatorValueError("type") + if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), tpe)) { + in.discriminatorValueError("type") + } } else if (in.isCharBufEqualsTo(len, "coordinates")) { if ((mask & 0x2) != 0) mask ^= 0x2 else in.duplicatedKeyError(len) @@ -105,27 +108,133 @@ object jsoniter_scala { out.writeObjectEnd() } - override def nullValue: G = null.asInstanceOf[G] - - private def error(in: JsonReader, mask: Int): Nothing = + private[this] def error(in: JsonReader, mask: Int): Nothing = in.requiredFieldError { - if ((mask & 0x2) == 0) "type" + if ((mask & 0x1) != 0) "type" else "coordinates" } } implicit val geometryCodec: JsonValueCodec[Geometry] = JsonCodecMaker.make - @nowarn - implicit def featureCodec[P: JsonValueCodec]: JsonValueCodec[Feature[P]] = JsonCodecMaker.make + implicit def featureCodec[P](implicit propertiesCodec: JsonValueCodec[P]): JsonValueCodec[Feature[P]] = + new JsonValueCodec[Feature[P]] { + override val nullValue: Feature[P] = null.asInstanceOf[Feature[P]] + + override def decodeValue(in: JsonReader, default: Feature[P]): Feature[P] = + if (in.isNextToken('{')) { + var properties: P = propertiesCodec.nullValue + var geometry: Geometry = geometryCodec.nullValue + var mask = 7 + var len = -1 + while (len < 0 || in.isNextToken(',')) { + len = in.readKeyAsCharBuf() + if (in.isCharBufEqualsTo(len, "type")) { + if ((mask & 0x1) != 0) mask ^= 0x1 + else in.duplicatedKeyError(len) + if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), "Feature")) { + in.discriminatorValueError("type") + } + } else if (in.isCharBufEqualsTo(len, "properties")) { + if ((mask & 0x2) != 0) mask ^= 0x2 + else in.duplicatedKeyError(len) + properties = propertiesCodec.decodeValue(in, properties) + } else if (in.isCharBufEqualsTo(len, "geometry")) { + if ((mask & 0x4) != 0) mask ^= 0x4 + else in.duplicatedKeyError(len) + geometry = geometryCodec.decodeValue(in, geometry) + } else in.skip() + } + if (!in.isCurrentToken('}')) in.objectEndOrCommaError() + if (mask != 0) error(in, mask) + new Feature(properties, geometry) + } else in.readNullOrTokenError(default, '}') + + override def encodeValue(x: Feature[P], out: JsonWriter): Unit = { + out.writeObjectStart() + out.writeNonEscapedAsciiKey("type") + out.writeNonEscapedAsciiVal("Feature") + out.writeNonEscapedAsciiKey("properties") + propertiesCodec.encodeValue(x.properties, out) + out.writeNonEscapedAsciiKey("geometry") + geometryCodec.encodeValue(x.geometry, out) + out.writeObjectEnd() + } + + private[this] def error(in: JsonReader, mask: Int): Nothing = + in.requiredFieldError { + if ((mask & 0x1) != 0) "type" + else if ((mask & 0x2) != 0) "properties" + else "geometry" + } + } - @nowarn - implicit def featureCollectionCodec[P: JsonValueCodec]: JsonValueCodec[FeatureCollection[P]] = JsonCodecMaker.make + implicit def featureCollectionCodec[P](implicit featureCodec: JsonValueCodec[Feature[P]]): JsonValueCodec[FeatureCollection[P]] = + new JsonValueCodec[FeatureCollection[P]] { + override val nullValue: FeatureCollection[P] = null.asInstanceOf[FeatureCollection[P]] + + override def decodeValue(in: JsonReader, default: FeatureCollection[P]): FeatureCollection[P] = + if (in.isNextToken('{')) { + var features: List[Feature[P]] = Nil + var mask = 3 + var len = -1 + while (len < 0 || in.isNextToken(',')) { + len = in.readKeyAsCharBuf() + if (in.isCharBufEqualsTo(len, "type")) { + if ((mask & 0x1) != 0) mask ^= 0x1 + else in.duplicatedKeyError(len) + if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), "FeatureCollection")) { + in.discriminatorValueError("type") + } + } else if (in.isCharBufEqualsTo(len, "features")) { + if ((mask & 0x2) != 0) mask ^= 0x2 + else in.duplicatedKeyError(len) + if (in.isNextToken('[')) { + if (!in.isNextToken(']')) { + in.rollbackToken() + val buf = new ListBuffer[Feature[P]] + while ({ + buf.addOne(featureCodec.decodeValue(in, featureCodec.nullValue)) + in.isNextToken(',') + }) () + if (in.isCurrentToken(']')) features = buf.toList + else in.arrayEndOrCommaError() + } else in.readNullOrTokenError(features, '[') + } + } else in.skip() + } + if (!in.isCurrentToken('}')) in.objectEndOrCommaError() + if (mask != 0) error(in, mask) + new FeatureCollection(features) + } else in.readNullOrTokenError(default, '}') + + override def encodeValue(x: FeatureCollection[P], out: JsonWriter): Unit = { + out.writeObjectStart() + out.writeNonEscapedAsciiKey("type") + out.writeNonEscapedAsciiVal("FeatureCollection") + out.writeNonEscapedAsciiKey("features") + out.writeArrayStart() + var remainingFeatures = x.features + while (remainingFeatures ne Nil) { + featureCodec.encodeValue(remainingFeatures.head, out) + remainingFeatures = remainingFeatures.tail + } + out.writeArrayEnd() + out.writeObjectEnd() + } + + private[this] def error(in: JsonReader, mask: Int): Nothing = + in.requiredFieldError { + if ((mask & 0x1) != 0) "type" + else "features" + } + } implicit def geoJson[P: JsonValueCodec]: JsonValueCodec[GeoJson[P]] = new JsonValueCodec[GeoJson[P]] { - private val fc: JsonValueCodec[Feature[P]] = featureCodec - private val fcc: JsonValueCodec[FeatureCollection[P]] = featureCollectionCodec + private[this] val fc: JsonValueCodec[Feature[P]] = featureCodec + private[this] val fcc: JsonValueCodec[FeatureCollection[P]] = featureCollectionCodec + override val nullValue: GeoJson[P] = null.asInstanceOf[GeoJson[P]] override def decodeValue(in: JsonReader, default: GeoJson[P]): GeoJson[P] = { in.setMark() @@ -150,7 +259,5 @@ object jsoniter_scala { case fc: FeatureCollection[P] => fcc.encodeValue(fc, out) case _ => geometryCodec.encodeValue(x.asInstanceOf[Geometry], out) } - - override def nullValue: GeoJson[P] = null.asInstanceOf[GeoJson[P]] } } diff --git a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala new file mode 100644 index 0000000..4e78bb0 --- /dev/null +++ b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2019 GHM Mobile Development GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.free2move.geoscala + +import com.free2move.geoscala.jsoniter_scala._ +import com.github.plokhotnyuk.jsoniter_scala.core._ +import com.github.plokhotnyuk.jsoniter_scala.macros._ +import org.scalatest.EitherValues +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class JsoniterScalaDecodingAndEncodingTests extends AnyFlatSpec with Matchers with EitherValues { + "The jsoniter-scala codecs" should "parse and serialize simple 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + readFromString[Point](json) shouldBe obj + readFromString[Geometry](json) shouldBe obj + writeToString[Point](obj) shouldBe json + writeToString[Geometry](obj) shouldBe json + } + + it should "parse points with more dimensions as 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775,7.000,42.12345]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + readFromString[Point](json) shouldBe obj + readFromString[Geometry](json) shouldBe obj + } + + it should "pare and serialize FeatureCollection using provided codec for properties" in { + type Json = Map[String, Int] + + implicit val jsonCodec: JsonValueCodec[Json] = JsonCodecMaker.make + + val json = + """{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":7},"geometry":{"type":"Point","coordinates":[12.3046875,51.8357775]}}]}""" + val obj = + FeatureCollection(List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775))))) + readFromString[FeatureCollection[Json]](json) shouldBe obj + readFromString[GeoJson[Json]](json) shouldBe obj + writeToString[FeatureCollection[Json]](obj) shouldBe json + writeToString[GeoJson[Json]](obj) shouldBe json + } +} diff --git a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala deleted file mode 100644 index c1c7519..0000000 --- a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2019 GHM Mobile Development GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.free2move.geoscala - -import com.free2move.geoscala.jsoniter_scala._ -import com.github.plokhotnyuk.jsoniter_scala.core._ -import com.github.plokhotnyuk.jsoniter_scala.macros._ -import org.scalatest.EitherValues -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class JsoniterScalaDecodingTests extends AnyFlatSpec with Matchers with EitherValues { - "The jsoniter-scala codecs" should "handle simple 2D points" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - }""" - readFromString[Point](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - readFromString[Geometry](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - } - - it should "handle points with more dimensions" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775, - 7.000, - 42.12345 - ] - }""" - readFromString[Point](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - readFromString[Geometry](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - } - - it should "handle FeatureCollection without Properties as pure JSON correctly" in { - type Json = Map[String, Int] - - implicit val jsonCodec: JsonValueCodec[Json] = JsonCodecMaker.make - - val json = - """{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "id": 7 - }, - "geometry": { - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - } - } - ] - }""" - readFromString[FeatureCollection[Json]](json) shouldBe FeatureCollection( - List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - readFromString[GeoJson[Json]](json) shouldBe FeatureCollection( - List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - } - -}