Skip to content
This repository has been archived by the owner on Nov 18, 2022. It is now read-only.

Commit

Permalink
Merge a10234a into cd8af5f
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Grigorev committed Dec 5, 2017
2 parents cd8af5f + a10234a commit 9174ddf
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 48 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.3.3-SHAPSHOT]
## [0.4-SHAPSHOT]
### Changed
- using scala CanBuildFrom for support any sequence and map

## [0.3.2] - 2017-11-16
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,33 @@ import com.github.sergeygrigorev.util.data.ElementDecoder
import com.google.gson.{ JsonArray, JsonObject }

import scala.collection.JavaConverters._
import scala.collection.generic.CanBuildFrom
import scala.language.higherKinds

/**
* Scala collections.
*/
trait JsonCollections {
import ElementDecoder.primitive

type StringMap[A] = Map[String, A]

implicit def jsonListDecoder[T: ElementDecoder]: ElementDecoder[List[T]] =
primitive[List[T]] {
case j: JsonArray => j.asScala.view.map(ElementDecoder[T].decode(_)).toList
implicit def sequenceDecoder[E: ElementDecoder, S[_]](implicit builder: CanBuildFrom[Nothing, E, S[E]]): ElementDecoder[S[E]] =
primitive[S[E]] {
case j: JsonArray =>
val res = builder()
val decoder = ElementDecoder[E]
for (v <- j.asScala)
res.+=(decoder.decode(v))
res.result()
case field => throw new IllegalArgumentException(s"element is not a collection ($field)")
}

implicit def hashMapDecoder[T: ElementDecoder]: ElementDecoder[StringMap[T]] =
primitive[StringMap[T]] {
case j: JsonObject => j.entrySet().asScala.view.map(e => (e.getKey, ElementDecoder[T].decode(e.getValue))).toMap
implicit def mapDecoder[T: ElementDecoder, M[_, _]](implicit builder: CanBuildFrom[Nothing, (String, T), M[String, T]]): ElementDecoder[M[String, T]] =
primitive[M[String, T]] {
case j: JsonObject =>
val res = builder()
for (e <- j.entrySet().asScala)
res += ((e.getKey, ElementDecoder[T].decode(e.getValue)))
res.result()
case field => throw new IllegalArgumentException(s"element couldn't be decodes as a map ($field)")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import com.github.sergeygrigorev.util.data.{ ElementDecoder, FieldDecoder }
import com.github.sergeygrigorev.util.instances.gson._
import com.google.gson.{ JsonElement, JsonObject }
import shapeless.labelled.FieldType
import shapeless.{ :+:, ::, CNil, Coproduct, HList, HNil, LabelledGeneric, Lazy, Witness, labelled }
import shapeless._

/**
* Shapeless decoders.
*/
trait JsonShapelessDecoder {
// HList
implicit def hnilDecoder: ElementDecoder[HNil] =
primitive[HNil](_ => HNil)

Expand All @@ -44,47 +45,35 @@ trait JsonShapelessDecoder {
}
}

implicit def genericHListDecoder[A, R <: HList](implicit
gen: LabelledGeneric.Aux[A, R],
dec: Lazy[ElementDecoder[R]]): ElementDecoder[A] =
primitive[A] { json: JsonElement =>
val hlist = dec.value.decode(json)
gen.from(hlist)
}

type CoproductMapType = Map[String, ElementDecoder[_]]

trait CoproductMap[F] {
def map: CoproductMapType
}

implicit val cnilMap: CoproductMap[CNil] =
new CoproductMap[CNil] {
override val map: CoproductMapType = Map.empty
// Coproduct
implicit val cnilDecoder: ElementDecoder[CNil] =
primitive[CNil] {
case j: JsonObject =>
val `type` = FieldDecoder[String].decode(j, "type")
throw new IllegalArgumentException(s"unknown type ${`type`} in $j")
}

implicit def coproductMap[K <: Symbol, H, T <: Coproduct](
implicit def coproductDecoder[K <: Symbol, H, T <: Coproduct](
implicit
witness: Witness.Aux[K],
hval: Lazy[ElementDecoder[H]],
tail: CoproductMap[T]): CoproductMap[FieldType[K, H] :+: T] = {
val name = witness.value.name
new CoproductMap[FieldType[K, H] :+: T] {
override val map: CoproductMapType = tail.map + (name -> hval.value)
}
}
hDecoder: Lazy[ElementDecoder[H]],
tDecoder: ElementDecoder[T]): ElementDecoder[FieldType[K, H] :+: T] = {

implicit def coproductGeneric[A, R <: Coproduct](
implicit
gen: LabelledGeneric.Aux[A, R],
map: Lazy[CoproductMap[R]]): ElementDecoder[A] =
primitive[A] {
val fieldName = witness.value.name
primitive[FieldType[K, H] :+: T] {
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")
}
if (`type` == fieldName) Inl(labelled.field[K](hDecoder.value.decode(j)))
else Inr(tDecoder.decode(j))
case e => throw new IllegalArgumentException(s"element $e is not an object")
}
}

// Generic
implicit def genericDecoder[A, R](implicit
gen: LabelledGeneric.Aux[A, R],
dec: Lazy[ElementDecoder[R]]): ElementDecoder[A] =
primitive[A] { json: JsonElement =>
gen.from(dec.value.decode(json))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.github.sergeygrigorev.util.syntax.gson._
import com.google.gson._
import org.scalatest.FlatSpec

import scala.collection.mutable

/**
* Examples and unit tests for [[com.github.sergeygrigorev.util.syntax]].
*/
Expand Down Expand Up @@ -71,14 +73,26 @@ class JsonObjectOpsTest extends FlatSpec {
assert(jsonObject.find[JsonObject]("a").isEmpty)
}

it should "decode list of primitives" in {
it should "decode sequences" in {
val jsonObject = parse("{a: [1, 2, 3] }")
assert(jsonObject.getAs[List[Int]]("a") == List(1, 2, 3))
assert(jsonObject.getAs[Set[Int]]("a") == Set(1, 2, 3))
assert(jsonObject.getAs[Stream[Int]]("a") == Stream(1, 2, 3))

import scala.collection.mutable
assert(jsonObject.getAs[mutable.Set[Int]]("a") == mutable.Set(1, 2, 3))
assert(jsonObject.getAs[mutable.ArrayBuffer[Int]]("a") == mutable.ArrayBuffer(1, 2, 3))
}

it should "decode map of primitives" in {
it should "decode maps" in {
val jsonObject = parse("{a: { b: 1, c: 2 } }")
assert(jsonObject.getAs[Map[String, Int]]("a") == Map("b" -> 1, "c" -> 2))
import scala.collection.immutable
assert(jsonObject.getAs[immutable.Map[String, Int]]("a") == immutable.Map("b" -> 1, "c" -> 2))
assert(jsonObject.getAs[immutable.TreeMap[String, Int]]("a") == immutable.TreeMap("b" -> 1, "c" -> 2))
assert(jsonObject.getAs[immutable.HashMap[String, Int]]("a") == immutable.HashMap("b" -> 1, "c" -> 2))

import scala.collection.mutable
assert(jsonObject.getAs[mutable.Map[String, Int]]("a") == mutable.Map("b" -> 1, "c" -> 2))
}

it should "decode custom type with manually created format" in {
Expand Down
2 changes: 1 addition & 1 deletion version.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version in ThisBuild := "0.3.3-SNAPSHOT"
version in ThisBuild := "0.4-SNAPSHOT"

0 comments on commit 9174ddf

Please sign in to comment.