Skip to content
This repository was archived by the owner on Nov 18, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2017 Sergey Grigorev
*
* 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.github.sergeygrigorev.util.instances

import com.github.sergeygrigorev.util.data.ElementDecoder
import com.github.sergeygrigorev.util.data.FieldDecoder
import com.github.sergeygrigorev.util.data.FieldDecoder.construct
import com.google.gson.JsonObject

import scala.language.higherKinds

/**
* [[ElementDecoder]] to [[FieldDecoder]]
*/
trait JsonElementToJsonDecoder {
/* ElementDecoder to FieldDecoder */
implicit def jsonElementToFieldDecoder[T: ElementDecoder]: FieldDecoder[T] =
construct[T] { (json: JsonObject, field: String) =>
if (json.has(field) && !json.get(field).isJsonNull) ElementDecoder[T].decode(json.get(field))
else throw new IllegalArgumentException(s"element $field is null and couldn't be decoded ($json)")
}

/* ElementDecoder to FieldDecoder (optional field) */
implicit def optionObjectDecoder[T: FieldDecoder]: FieldDecoder[Option[T]] =
construct[Option[T]] { (json: JsonObject, field: String) =>
if (json.has(field) && !json.get(field).isJsonNull) Some(FieldDecoder[T].decode(json, field))
else None
}

/* Higher Kind type F[T] */
implicit def tuple1ToFieldDecoder[F[_], T](implicit ev: ElementDecoder[F[T]]): FieldDecoder[F[T]] =
jsonElementToFieldDecoder(ev)

/* Higher Kind type F[T1, T2] */
implicit def tuple2ToFieldDecoder[F[_, _], T1, T2](implicit ev: ElementDecoder[F[T1, T2]]): FieldDecoder[F[T1, T2]] =
jsonElementToFieldDecoder(ev)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,34 @@
package com.github.sergeygrigorev.util.instances

import com.github.sergeygrigorev.util.data.{ ElementDecoder, FieldDecoder }
import com.google.gson.JsonObject
import com.google.gson.{ JsonArray, JsonElement, JsonObject, JsonPrimitive }

import scala.language.higherKinds

/**
* More specific decoders for [[FieldDecoder]].
*/
trait JsonObjects {
import FieldDecoder.construct
import ElementDecoder.primitive

implicit val jsonObjectDecoder: ElementDecoder[JsonObject] =
primitive[JsonObject] {
case j: JsonObject => j
case json => throw new IllegalArgumentException(s"element couldn't be decoded as object ($json)")
case json => throw new IllegalArgumentException(s"couldn't be decoded as JsonObject ($json)")
}

implicit def jsonElementToFieldDecoder[T: ElementDecoder]: FieldDecoder[T] =
construct[T] { (json: JsonObject, field: String) =>
if (json.has(field)) ElementDecoder[T].decode(json.get(field))
else throw new IllegalArgumentException(s"element $field is null and couldn't be decoded ($json)")
}

implicit def jsonCollectionToFieldDecoder[F[_], T](implicit ev: ElementDecoder[F[T]]): FieldDecoder[F[T]] =
construct[F[T]] { (json: JsonObject, field: String) =>
if (json.has(field)) ev.decode(json.get(field))
else throw new IllegalArgumentException(s"element $field is null and couldn't be decoded ($json)")
}
implicit val jsonElementDecoder: ElementDecoder[JsonElement] =
primitive[JsonElement] { j: JsonElement => j }

implicit def jsonMapToFieldDecoder[F[_, _], K, V](implicit ev: ElementDecoder[F[K, V]]): FieldDecoder[F[K, V]] =
construct[F[K, V]] { (json: JsonObject, field: String) =>
if (json.has(field)) ev.decode(json.get(field))
else throw new IllegalArgumentException(s"element $field is null and couldn't be decoded ($json)")
implicit val jsonArrayDecoder: ElementDecoder[JsonArray] =
primitive[JsonArray] {
case j: JsonArray => j
case json => throw new IllegalArgumentException(s"couldn't be decoded as JsonArray ($json)")
}

implicit def optionObjectDecoder[T: FieldDecoder]: FieldDecoder[Option[T]] =
construct[Option[T]] { (json: JsonObject, field: String) =>
if (json.has(field)) Some(FieldDecoder[T].decode(json, field))
else None
implicit val jsonPrimitiveDecoder: ElementDecoder[JsonPrimitive] =
primitive[JsonPrimitive] {
case j: JsonPrimitive => j
case json => throw new IllegalArgumentException(s"couldn't be decoded as JsonPrimitive ($json)")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import shapeless.{ :+:, ::, CNil, Coproduct, HList, HNil, LabelledGeneric, Lazy,
* Shapeless decoders.
*/
trait JsonShapelessDecoder {

implicit def hnilDecoder: ElementDecoder[HNil] =
primitive[HNil](_ => HNil)

Expand Down Expand Up @@ -80,12 +79,12 @@ trait JsonShapelessDecoder {
gen: LabelledGeneric.Aux[A, R],
map: Lazy[CoproductMap[R]]): ElementDecoder[A] =
primitive[A] {
case j: JsonObject if j.has("type") =>
case j: JsonObject =>
val `type` = FieldDecoder[String].decode(j, "type")
map.value.map.get(`type`) match {
case Some(decoder) => decoder.decode(j).asInstanceOf[A]
case None => throw new IllegalArgumentException(s"unknown type ${`type`} in $j")
}
case e => throw new IllegalArgumentException(s"element $e is not an object")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ package object instances {
with JsonShapelessDecoder
with JsonCollections
with JsonOtherTypes
with JsonElementToJsonDecoder
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package com.github.sergeygrigorev.util

import com.github.sergeygrigorev.util.instances.gson._
import com.github.sergeygrigorev.util.syntax.gson._
import com.google.gson.{ Gson, JsonObject, JsonParser }
import com.google.gson._
import org.scalatest.FlatSpec

/**
Expand Down Expand Up @@ -52,15 +52,23 @@ class JsonObjectOpsTest extends FlatSpec {

it should "decode some other simple types" in {
val jsonObject = parse("""{string: "string", number: 10, object: {} }""")
assert(jsonObject.getAs[JsonObject]("object") == jsonObject.get("object"))
assert(jsonObject.getAs[String]("string") == "string")
assert(jsonObject.getAs[BigDecimal]("number") == BigDecimal(10))
assert(jsonObject.getAs[BigInt]("number") == BigInt(10))
}

it should "decode base gson types" in {
val jsonObject = parse("""{number: 10, object: {}, array: [1, 2] }""")
assert(jsonObject.getAs[JsonObject]("object") == jsonObject.get("object"))
assert(jsonObject.getAs[JsonElement]("object") == jsonObject.get("object"))
assert(jsonObject.getAs[JsonArray]("array") == jsonObject.getAsJsonArray("array"))
assert(jsonObject.getAs[JsonPrimitive]("number") == jsonObject.getAsJsonPrimitive("number"))
}

it should "decode optional values" in {
val jsonObject = parse("{a: null}")
assert(jsonObject.find[Int]("b").isEmpty)
assert(jsonObject.find[JsonObject]("a").isEmpty)
}

it should "decode list of primitives" in {
Expand All @@ -75,13 +83,20 @@ class JsonObjectOpsTest extends FlatSpec {

it should "decode custom type with manually created format" in {
val jsonObject = parse("{a: { byte: 1, int: 2 } }")
import JsonObjectOpsTest.customTypeParser
assert(jsonObject.getAs[JsonObjectOpsTest.CustomType]("a") == JsonObjectOpsTest.CustomType(1, 2))
}

it should "decode custom type" in {
case class CustomType2(long: Long, double: Double, list: List[Int])
case class CustomType2(long: Long, double: Double)
val jsonObject = parse("{a: { long: 1, double: 2 } }")
assert(jsonObject.getAs[CustomType2]("a") == CustomType2(1, 2))
}

it should "decode custom complicated type" in {
case class CustomType2(long: Long, double: Double, list: Option[List[Int]])
val jsonObject = parse("{a: { long: 1, double: 2, list: [3, 4] } }")
assert(jsonObject.getAs[CustomType2]("a") == CustomType2(1, 2, List(3, 4)))
assert(jsonObject.getAs[CustomType2]("a") == CustomType2(1, 2, Some(List(3, 4))))
}

it should "decode custom type with optional fields" in {
Expand Down Expand Up @@ -126,10 +141,27 @@ class JsonObjectOpsTest extends FlatSpec {
}

it should "correctly throw expected exceptions on elements having incorrect types" in {
val jsonObject = parse("""{string: "string"}""")
val jsonObject = parse("""{string: "string", b: {} }""")
assertThrows[IllegalArgumentException](jsonObject.getAs[Map[String, String]]("string"))
assertThrows[IllegalArgumentException](jsonObject.getAs[List[String]]("string"))
assertThrows[IllegalArgumentException](jsonObject.getAs[JsonObject]("string"))
assertThrows[IllegalArgumentException](jsonObject.getAs[JsonArray]("string"))
assertThrows[IllegalArgumentException](jsonObject.getAs[JsonPrimitive]("b"))
}

it should "correctly throw expected exceptions on elements having incorrect types (Product)" in {
case class Inner(long: Long)
case class Outer(inner: Inner)
val jsonObject = parse("""{b: { inner: 10 } }""")
assertThrows[IllegalArgumentException](jsonObject.getAs[Outer]("b"))
}

it should "correctly throw expected exceptions on elements having incorrect types (Coproduct)" in {
sealed trait CustomTrait
case class CustomType1(long: Long) extends CustomTrait
case class CustomType2(int: Int) extends CustomTrait
val jsonObject = parse("""{b: 10 }""")
assertThrows[IllegalArgumentException](jsonObject.getAs[CustomTrait]("b"))
}

it should "correctly throw expected in HList code generation instances" in {
Expand Down Expand Up @@ -166,7 +198,7 @@ object JsonObjectOpsTest {
float: Float,
double: Double)

case class CustomType(byte: Byte, int: Int)
case class CustomType(b: Byte, i: Int)

/* manually created decoder */
import com.github.sergeygrigorev.util.data.ElementDecoder
Expand Down