Skip to content

Commit

Permalink
More features for Json En/Decoders (#18)
Browse files Browse the repository at this point in the history
More features for Json En/Decoders. Closes #14
  • Loading branch information
alterationx10 authored Dec 28, 2024
1 parent 53b3c68 commit 7c2a666
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ object Json {
JsonObject(fields.toMap)
}

/** Creates a JSON array from the given values
*/
def arr(values: Json*): JsonArray = {
JsonArray(values.toIndexedSeq)
}

/** Encode the vale to JSON, returning [[JsonNull]] on failure */
inline def jsonOrNull[A](a: => A)(using encoder: JsonEncoder[A]): Json = {
Try(encoder.encode(a)).getOrElse(JsonNull)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dev.wishingtree.branch.macaroni.meta.Summons.{
}

import java.time.Instant
import scala.collection.*
import scala.compiletime.*
import scala.deriving.Mirror
import scala.util.*
Expand Down Expand Up @@ -76,13 +77,44 @@ object JsonDecoder {
Try(json.numVal.toInt)
}

given JsonDecoder[Long] with {
def decode(json: Json): Try[Long] =
Try(json.numVal.toLong)
}

/** A JsonDecoder for Instant
*/
given JsonDecoder[Instant] with {
def decode(json: Json): Try[Instant] =
Try(Instant.parse(json.strVal))
}

private[friday] def iterableDecoder[A, F[_]](
builder: mutable.Builder[A, F[A]]
)(using decoder: JsonDecoder[A]): JsonDecoder[F[A]] =
(json: Json) =>
Try {
json.arrVal.foldLeft(builder)((b, j) => {
b.addOne(decoder.decode(j).get)
})
builder.result()
}

implicit def seqDecoder[A: JsonDecoder]: JsonDecoder[Seq[A]] =
iterableDecoder[A, Seq](Seq.newBuilder[A])

implicit def listDecoder[A: JsonDecoder]: JsonDecoder[List[A]] =
iterableDecoder[A, List](List.newBuilder[A])

implicit def indexedSeqDecoder[A: JsonDecoder]: JsonDecoder[IndexedSeq[A]] =
iterableDecoder[A, IndexedSeq](IndexedSeq.newBuilder[A])

implicit def setDecoder[A: JsonDecoder]: JsonDecoder[Set[A]] =
iterableDecoder[A, Set](Set.newBuilder[A])

implicit def vectorDecoder[A: JsonDecoder]: JsonDecoder[Vector[A]] =
iterableDecoder[A, Vector](Vector.newBuilder[A])

private[friday] inline def buildJsonProduct[A](
p: Mirror.ProductOf[A],
b: Json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,50 @@ class JsonDecoderSpec extends FunSuite {
} yield assertEquals(person, Person("Alice", 42))

}

test("JsonDecoder Json => Seq[A]") {

val decoder = JsonDecoder.seqDecoder[String]
val json = Json.arr(JsonString("Alice"), JsonString("Bob"))
for {
seq <- decoder.decode(json)
} yield assertEquals(seq, Seq("Alice", "Bob"))
}

test("JsonDecoder Json => List[A]") {

val decoder = JsonDecoder.listDecoder[String]
val json = Json.arr(JsonString("Alice"), JsonString("Bob"))
for {
list <- decoder.decode(json)
} yield assertEquals(list, List("Alice", "Bob"))
}

test("JsonDecoder Json => Set[A]") {

val decoder = JsonDecoder.setDecoder[String]
val json = Json.arr(JsonString("Alice"), JsonString("Bob"))
for {
set <- decoder.decode(json)
} yield assertEquals(set, Set("Alice", "Bob"))
}

test("JsonDecoder Json => IndexedSeq[A]") {

val decoder = JsonDecoder.indexedSeqDecoder[String]
val json = Json.arr(JsonString("Alice"), JsonString("Bob"))
for {
indexedSeq <- decoder.decode(json)
} yield assertEquals(indexedSeq, IndexedSeq("Alice", "Bob"))
}

test("JsonDecoder Json => Vector[A]") {

val decoder = JsonDecoder.vectorDecoder[String]
val json = Json.arr(JsonString("Alice"), JsonString("Bob"))
for {
vector <- decoder.decode(json)
} yield assertEquals(vector, Vector("Alice", "Bob"))
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package dev.wishingtree.branch.friday

import dev.wishingtree.branch.friday.Json.{JsonObject, JsonString}
import dev.wishingtree.branch.friday.Json.{JsonArray, JsonObject, JsonString}
import dev.wishingtree.branch.macaroni.meta.Summons.summonHigherListOf

import java.time.Instant
import scala.compiletime.*
import scala.deriving.Mirror
import scala.reflect.ClassTag

/** A type-class for encoding values to Json
*/
Expand Down Expand Up @@ -82,12 +83,49 @@ object JsonEncoder {
def encode(a: Int): Json = Json.JsonNumber(a.toDouble)
}

/** A JsonEncoder for Longs
*/
given JsonEncoder[Long] with {
def encode(a: Long): Json = Json.JsonNumber(a.toDouble)
}

/** A JsonEncoder for Instants
*/
given JsonEncoder[Instant] with {
def encode(a: Instant): Json = JsonString(a.toString)
}

/** Helper method for collection/iterable JsonEncoders */
private[friday] def iterableEncoder[A, F[X] <: Iterable[X]](using
jsonEncoder: JsonEncoder[A]
): JsonEncoder[F[A]] =
(a: F[A]) => JsonArray(a.iterator.map(jsonEncoder.encode).toIndexedSeq)

/** A JsonEncoder for Seqs
*/
implicit def seqEncoder[A: JsonEncoder]: JsonEncoder[Seq[A]] =
iterableEncoder[A, Seq]

/** A JsonEncoder for Lists
*/
implicit def listEncoder[A: JsonEncoder]: JsonEncoder[List[A]] =
iterableEncoder[A, List]

/** A JsonEncoder for IndexedSeqs
*/
implicit def indexedSeqEncoder[A: JsonEncoder]: JsonEncoder[IndexedSeq[A]] =
iterableEncoder[A, IndexedSeq]

/** A JsonEncoder for Sets
*/
implicit def setEncoder[A: JsonEncoder]: JsonEncoder[Set[A]] =
iterableEncoder[A, Set]

/** A JsonEncoder for Vectors
*/
implicit def vectorEncoder[A: JsonEncoder]: JsonEncoder[Vector[A]] =
iterableEncoder[A, Vector]

private[friday] inline def buildJsonProduct[A](
a: A
)(using m: Mirror.Of[A]): Json = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,36 @@ class JsonEncoderSpec extends FunSuite {
)
}

test("JsonEncoder A => Seq[A]") {
val strSeqEncoder = summon[JsonEncoder[Seq[String]]]
assertEquals(
strSeqEncoder.encode(Seq("Alice", "Bob")),
Json.arr(JsonString("Alice"), JsonString("Bob"))
)
}

test("JsonEncoder A => List[A]") {
val strListEncoder = summon[JsonEncoder[List[String]]]
assertEquals(
strListEncoder.encode(List("Alice", "Bob")),
Json.arr(JsonString("Alice"), JsonString("Bob"))
)
}

test("JsonEncoder A => Vector[A]") {
val strVectorEncoder = summon[JsonEncoder[Vector[String]]]
assertEquals(
strVectorEncoder.encode(Vector("Alice", "Bob")),
Json.arr(JsonString("Alice"), JsonString("Bob"))
)
}

test("JsonEncoder A => Set[A]") {
val strSetEncoder = summon[JsonEncoder[Set[String]]]
assertEquals(
strSetEncoder.encode(Set("Alice", "Bob")),
Json.arr(JsonString("Alice"), JsonString("Bob"))
)
}

}

0 comments on commit 7c2a666

Please sign in to comment.