Skip to content

Commit

Permalink
Add missing tests for serialization and fix codecs to pass them
Browse files Browse the repository at this point in the history
  • Loading branch information
plokhotnyuk committed Sep 21, 2022
1 parent 2149dba commit df773a9
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 204 deletions.
31 changes: 17 additions & 14 deletions circe/src/main/scala/com/free2move/geoscala/circe.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = {
Expand Down Expand Up @@ -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('{')) {
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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]]
}
}

0 comments on commit df773a9

Please sign in to comment.