Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More features for Json En/Decoders #18

Merged
merged 2 commits into from
Dec 28, 2024
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
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"))
)
}

}