Skip to content

Commit

Permalink
✨ Improve List Deserialization Definition (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
kittinunf committed Oct 9, 2017
1 parent 87e57f5 commit c5221c9
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 55 deletions.
12 changes: 10 additions & 2 deletions README.md
Expand Up @@ -49,13 +49,15 @@ Given you have JSON as such
}
```

You can write your Kotlin class definition as such

``` Kotlin
data class User(val id: Int,
val name: String,
val age: Int,
val email: String?,
val friends: List<DeserializedResult<User>>,
val dogs: List<DeserializedResult<Dog>>?)
val friends: List<User>,
val dogs: List<Dog>?)

data class Dog(val name: String, val breed: String, val male: Boolean)

Expand All @@ -75,6 +77,12 @@ val dogDeserializer = { json: JSON ->
apply(json at "is_male")
}

```

Viola!, then, you can deserialize your JSON like

``` Kotlin

//jsonContent is when you receive data as a JSON
val result = Forge.modelFromJson(jsonContent, ::userDeserializer)

Expand Down
Expand Up @@ -5,23 +5,28 @@ sealed class DeserializedResult<out T : Any?> {
operator abstract fun component1(): T?
operator abstract fun component2(): Exception?

fun fold(ft: (T?) -> Unit, fe: (Exception) -> Unit) {
inline fun fold(ft: (T?) -> Unit, fe: (Exception) -> Unit) {
return when (this) {
is Success<T> -> ft(this.value)
is Failure<T> -> fe(this.error)
}
}

fun <U> map(f: (T) -> U): DeserializedResult<U> = when (this) {
inline fun <U> map(f: (T) -> U): DeserializedResult<U> = when (this) {
is DeserializedResult.Success -> DeserializedResult.Success(f(this.get()))
is DeserializedResult.Failure -> DeserializedResult.Failure(this.get())
}

fun <X> let(f: (T) -> X): X? = when (this) {
inline fun <X> let(f: (T) -> X): X? = when (this) {
is Success<T> -> f(this.get())
is Failure<T> -> null
}

inline fun <U> flatMap(fm: (T?) -> DeserializedResult<U>): DeserializedResult<U> = when (this) {
is DeserializedResult.Success -> fm(this.value)
is DeserializedResult.Failure -> DeserializedResult.Failure(this.error)
}

@Suppress("UNCHECKED_CAST")
fun <X> get(): X = when (this) {
is Success<T> -> this.value as X
Expand Down
22 changes: 14 additions & 8 deletions forge/src/main/kotlin/com/github/kittinunf/forge/core/Operators.kt
@@ -1,5 +1,7 @@
package com.github.kittinunf.forge.core

import com.github.kittinunf.forge.extension.lift

infix fun <T, U> Function1<T, U>.map(deserializedResult: DeserializedResult<T>) = deserializedResult.map(this)

fun <T, U> DeserializedResult<(T) -> U>.apply(deserializedResult: DeserializedResult<T>): DeserializedResult<U> =
Expand All @@ -20,16 +22,20 @@ infix fun <T> JSON.at(key: String): DeserializedResult<T> = at(key) { valueAs<T>

infix fun <T> JSON.maybeAt(key: String): DeserializedResult<T> = maybeAt(key) { valueAs<T>() }

fun <T> JSON.list(key: String, deserializer: JSON.() -> DeserializedResult<T>): DeserializedResult<List<DeserializedResult<T>>> {
return find(key)?.map(deserializer)?.toList()?.let { DeserializedResult.Success(it) } ?:
DeserializedResult.Failure(PropertyNotFoundException(key))
fun <T> JSON.list(key: String, deserializer: JSON.() -> DeserializedResult<T>): DeserializedResult<List<T>> {
return find(key)?.map(deserializer)
?.toList()
?.lift()
?: DeserializedResult.Failure(PropertyNotFoundException(key))
}

fun <T> JSON.maybeList(key: String, deserializer: JSON.() -> DeserializedResult<T>): DeserializedResult<List<DeserializedResult<T>>> {
return find(key)?.map(deserializer)?.toList()?.let { DeserializedResult.Success(it) } ?:
DeserializedResult.Success(null)
fun <T> JSON.maybeList(key: String, deserializer: JSON.() -> DeserializedResult<T>): DeserializedResult<List<T>> {
return find(key)?.map(deserializer)
?.toList()
?.lift()
?: DeserializedResult.Success(null)
}

fun <T> JSON.list(key: String) = list(key) { valueAs<T>() }
infix fun <T> JSON.list(key: String) = list(key) { valueAs<T>() }

fun <T> JSON.maybeList(key: String) = list(key) { valueAs<T>() }
infix fun <T> JSON.maybeList(key: String) = list(key) { valueAs<T>() }
@@ -0,0 +1,15 @@
package com.github.kittinunf.forge.extension

import com.github.kittinunf.forge.core.DeserializedResult

fun <U, T : DeserializedResult<U>> List<T>.lift(): DeserializedResult<List<U>> {
return fold(DeserializedResult.Success(mutableListOf<U>()) as DeserializedResult<List<U>>) { acc, each ->
acc.flatMap { arr ->
each.map {
(arr as MutableList).apply {
add(it)
}
}
}
}
}
82 changes: 82 additions & 0 deletions forge/src/test/kotlin/CurryingTest.kt
@@ -0,0 +1,82 @@
import com.github.kittinunf.forge.util.create
import com.github.kittinunf.forge.util.curry

import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test

class CurryingTest {

@Test
fun testCurrying2() {
val multiply = { x: Int, y: Int -> x * y }
val curry = multiply.create
assertThat(curry(2)(3), equalTo(6))
}

@Test
fun testCurrying3() {
val concat = { x: String, y: String, z: String -> x + y + z }
assertThat(concat.curry()("a")("bb")("ccc"), equalTo("abbccc"))
}

@Test
fun testCurrying4() {
val multiply = { w: Int, x: Int, y: Int, z: Int -> w * x * y * z }
val curry = multiply.create
assertThat(curry(1)(2)(3)(4), equalTo(24))
}

@Test
fun testCurrying5() {
val concat = { v: String, w: String, x: String, y: String, z: String -> v + w + x + y + z }
assertThat(concat.curry()("a")("bb")("ccc")("dddd")("eeeee"), equalTo("abbcccddddeeeee"))
}

@Test
fun testCurrying6() {
val multiply = { u: Int, v: Int, w: Int, x: Int, y: Int, z: Int -> u * v * w * x * y * z }
val curry = multiply.create
assertThat(curry(1)(2)(3)(4)(5)(6), equalTo(720))
}

@Test
fun testCurrying7() {
val concat = { t: String, u: String, v: String, w: String, x: String, y: String, z: String -> t + u + v + w + x + y + z }
assertThat(concat.curry()("a")("bb")("ccc")("dddd")("eeeee")("f")("gg"), equalTo("abbcccddddeeeeefgg"))
}

@Test
fun testCurrying8() {
val multiply = { s: Int, t: Int, u: Int, v: Int, w: Int, x: Int, y: Int, z: Int -> s * t * u * v * w * x * y * z }
val curry = multiply.create
assertThat(curry(1)(2)(3)(4)(5)(6)(7)(8), equalTo(40_320))
}

@Test
fun testCurrying9() {
val concat = { r: String, s: String, t: String, u: String, v: String, w: String, x: String, y: String, z: String -> r + s + t + u + v + w + x + y + z }
assertThat(concat.curry()("a")("bb")("ccc")("dddd")("eeeee")("f")("gg")("hhh")("iiii"), equalTo("abbcccddddeeeeefgghhhiiii"))
}

@Test
fun testCurrying10() {
val multiply = { q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int, x: Int, y: Int, z: Int -> q * r * s * t * u * v * w * x * y * z }
val curry = multiply.create
assertThat(curry(1)(2)(3)(4)(5)(6)(7)(8)(9)(10), equalTo(3_628_800))
}

@Test
fun testCurrying11() {
val concat = { p: String, q: String, r: String, s: String, t: String, u: String, v: String, w: String, x: String, y: String, z: String -> p + q + r + s + t + u + v + w + x + y + z }
assertThat(concat.curry()("a")("bb")("ccc")("dddd")("eeeee")("f")("gg")("hhh")("iiii")("jjjjj")("k"), equalTo("abbcccddddeeeeefgghhhiiiijjjjjk"))
}

@Test
fun testCurrying12() {
val multiply = { o: Int, p: Int, q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int, x: Int, y: Int, z: Int -> o * p * q * r * s * t * u * v * w * x * y * z }
val curry = multiply.create
assertThat(curry(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12), equalTo(479_001_600))
}

}
33 changes: 16 additions & 17 deletions forge/src/test/kotlin/JSONMappingArrayTest.kt
Expand Up @@ -78,7 +78,7 @@ class JSONMappingArrayTest : BaseTest() {
apply(j at "is_male")
}

data class UserWithDogs(val email: String, val phone: String, val dogs: List<DeserializedResult<Dog>>?)
data class UserWithDogs(val email: String, val phone: String, val dogs: List<Dog>?)

val userWithDogDeserializer = { j: JSON ->
::UserWithDogs.create.
Expand All @@ -90,38 +90,37 @@ class JSONMappingArrayTest : BaseTest() {
@Test
fun testUserWithDogsArrayDeserializing() {
val users = Forge.modelsFromJson(usersJson, userWithDogDeserializer)
val dogs = users.map { r: DeserializedResult<UserWithDogs> ->
r.map { it.dogs }.let { it?.map { it.get<Dog>() } }
}

val dogs = users.map { it.map(UserWithDogs::dogs).get<List<Dog>>() }

val firstUser = users[0].get<UserWithDogs>()
assertThat(firstUser.email, equalTo("Sincere@april.biz"))
assertThat(dogs[0]!!.size, equalTo(1))
assertThat(dogs[0]!!.first().name, equalTo("Lucy"))
assertThat(dogs[0].size, equalTo(1))
assertThat(dogs[0].first().name, equalTo("Lucy"))

val secondUser = users[1].get<UserWithDogs>()
assertThat(secondUser.phone, equalTo("010-692-6593 x09125"))
assertThat(dogs[1]!!.size, equalTo(2))
assertThat(dogs[1]!!.first().breed, equalTo("Rottweiler"))
assertThat(dogs[1]!!.last().name, equalTo("Maggie"))
assertThat(dogs[1].size, equalTo(2))
assertThat(dogs[1].first().breed, equalTo("Rottweiler"))
assertThat(dogs[1].last().name, equalTo("Maggie"))

val thirdUser = users[2].get<UserWithDogs>()
assertThat(thirdUser.email, equalTo("Nathan@yesenia.net"))
assertThat(dogs[2], nullValue())

val fourthUser = users[3].get<UserWithDogs>()
assertThat(fourthUser.email, equalTo("Julianne.OConner@kory.org"))
assertThat(dogs[3]!!.size, equalTo(1))
assertThat(dogs[3]!!.first().name, equalTo("Cooper"))
assertThat(dogs[3]!!.first().male, equalTo(true))
assertThat(dogs[3].size, equalTo(1))
assertThat(dogs[3].first().name, equalTo("Cooper"))
assertThat(dogs[3].first().male, equalTo(true))

val fifthUser = users[4].get<UserWithDogs>()
assertThat(fifthUser.phone, equalTo("(254)954-1289"))
assertThat(dogs[4]!!.size, equalTo(4))
assertThat(dogs[4]!!.first().breed, equalTo("Yorkshire Terrier"))
assertThat(dogs[4]!![1].name, equalTo("Max"))
assertThat(dogs[4]!![2].name, equalTo("Daisy"))
assertThat(dogs[4]!![3].male, equalTo(false))
assertThat(dogs[4].size, equalTo(4))
assertThat(dogs[4].first().breed, equalTo("Yorkshire Terrier"))
assertThat(dogs[4][1].name, equalTo("Max"))
assertThat(dogs[4][2].name, equalTo("Daisy"))
assertThat(dogs[4][3].male, equalTo(false))
}

}
23 changes: 7 additions & 16 deletions forge/src/test/kotlin/JSONMappingObjectTest.kt
Expand Up @@ -20,16 +20,6 @@ import java.util.*

class JSONMappingObjectTest : BaseTest() {

@Test
fun testCurrying() {
val multiply = { x: Int, y: Int -> x * y }
val curry = multiply.curry()
assertThat(curry(2)(3), equalTo(6))

val concatOfThree = { x: String, y: String, z: String -> x + y + z }
assertThat(concatOfThree.curry()("a")("bb")("ccc"), equalTo("abbccc"))
}

data class SimpleUser(val id: Int, val name: String) {

class Deserializer : Deserializable<SimpleUser> {
Expand All @@ -49,6 +39,7 @@ class JSONMappingObjectTest : BaseTest() {

assertThat(user!!.id, equalTo(1))
assertThat(user.name, equalTo("Clementina DuBuque"))
assertThat(ex, nullValue())
}

data class User(val id: Int, val name: String, val age: Int, val email: String) {
Expand Down Expand Up @@ -117,10 +108,10 @@ class JSONMappingObjectTest : BaseTest() {
val user = curry(name.get())(city.get())(gender.get())(phone.get())(weight.get())

assertThat(user.name, equalTo("Clementina DuBuque"))
assertThat(user.city, nullValue())
assertThat(user.gender, nullValue())
assertThat(user.phone, equalTo("024-648-3804"))
assertThat(user.weight, equalTo(72.5f))
assertThat(user.city, nullValue())
assertThat(user.gender, nullValue())
}

@Test
Expand Down Expand Up @@ -254,7 +245,7 @@ class JSONMappingObjectTest : BaseTest() {

}

data class UserWithFriends(val id: Int, val name: String, val age: Int, val email: String, val friends: List<DeserializedResult<Friend>>) {
data class UserWithFriends(val id: Int, val name: String, val age: Int, val email: String, val friends: List<Friend>) {

class Deserializer : Deserializable<UserWithFriends> {
override val deserializer: (JSON) -> DeserializedResult<UserWithFriends> = { json ->
Expand All @@ -277,16 +268,16 @@ class JSONMappingObjectTest : BaseTest() {
assertThat(user, notNullValue())
assertThat(user.friends.size, equalTo(3))

val first = user.friends[0].get<Friend>()
val first = user.friends[0]
assertThat(first.id, equalTo(10))
assertThat(first.name, equalTo("Leanne Graham"))


val second = user.friends[1].get<Friend>()
val second = user.friends[1]
assertThat(second.name, equalTo("Ervin Howell"))
assertThat(second.address.street, equalTo("Victor Plains"))

val third = user.friends[2].get<Friend>()
val third = user.friends[2]
assertThat(third.address.street, equalTo("Douglas Extension"))
assertThat(third.address.suite, equalTo("Suite 847"))
}
Expand Down
18 changes: 9 additions & 9 deletions forge/src/test/kotlin/JSONObjectTest.kt
Expand Up @@ -28,27 +28,27 @@ class JSONObjectTest : BaseTest() {
fun testJSONValidValue() {
val json = JSON.parse((JSONObject(userJson)))

val id: DeserializedResult<Int>? = json.find("id")?.valueAs()
val id = json.find("id")?.valueAs<Int>()
assertThat(id, notNullValue())
assertThat(id!!.get<Int>(), equalTo(1))

val name: DeserializedResult<String>? = json.find("name")?.valueAs()
val name = json.find("name")?.valueAs<String>()

assertThat(name, notNullValue())
assertThat(name!!.get<String>(), equalTo("Clementina DuBuque"))

val isDeleted: DeserializedResult<Boolean>? = json.find("is_deleted")?.valueAs()
val isDeleted = json.find("is_deleted")?.valueAs<Boolean>()

assertThat(isDeleted, notNullValue())
assertThat(isDeleted!!.get<Boolean>(), equalTo(true))

val addressStreet: DeserializedResult<String>? = json.find("address.street")?.valueAs()
val addressStreet = json.find("address.street")?.valueAs<String>()

assertThat(addressStreet, notNullValue())
assertThat(addressStreet!!.get<String>(), equalTo("Kattie Turnpike"))


val addressGeoLat: DeserializedResult<Double>? = json.find("address.geo.lat")?.valueAs()
val addressGeoLat = json.find("address.geo.lat")?.valueAs<Double>()

assertThat(addressGeoLat, notNullValue())
assertThat(addressGeoLat!!.get<Double>(), equalTo(-38.2386))
Expand All @@ -58,17 +58,17 @@ class JSONObjectTest : BaseTest() {
fun testJSONInvalidValue() {
val json = JSON.parse((JSONObject(userJson)))

val notFoundName: DeserializedResult<String>? = json.find("n")?.valueAs() ?:
val notFoundName = json.find("n")?.valueAs<String>() ?:
DeserializedResult.Failure(PropertyNotFoundException("n"))

assertThat(notFoundName, notNullValue())
assertThat(notFoundName!!.get<Exception>(), instanceOf(PropertyNotFoundException::class.java))
assertThat(notFoundName.get<Exception>(), instanceOf(PropertyNotFoundException::class.java))

val notFoundAddressSt: DeserializedResult<String>? = json.find("address.st")?.valueAs<String>() ?:
val notFoundAddressSt = json.find("address.st")?.valueAs<String>() ?:
DeserializedResult.Failure(PropertyNotFoundException("address.st"))

assertThat(notFoundAddressSt, notNullValue())
assertThat(notFoundAddressSt!!.get<Exception>(), instanceOf(PropertyNotFoundException::class.java))
assertThat(notFoundAddressSt.get<Exception>(), instanceOf(PropertyNotFoundException::class.java))
}

}

0 comments on commit c5221c9

Please sign in to comment.