Skip to content

Commit

Permalink
ADD: JsonReader null validator testcase
Browse files Browse the repository at this point in the history
  • Loading branch information
RicardoJiang committed Nov 14, 2023
1 parent 0e33962 commit 6393d65
Show file tree
Hide file tree
Showing 16 changed files with 383 additions and 49 deletions.
4 changes: 4 additions & 0 deletions .github/scripts/tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ do
./gradlew :kudos-compiler:test -PJACKSON_VERSION=$jackson_version -PVARIANT=jackson -PKOTLIN_COMPILER=K2
done

# Android JsonReader
echo "[Kudos] Testing with Android JsonReader"
./gradlew :kudos-compiler:test -PVARIANT=jsonReader -PKOTLIN_COMPILER=K2

cd -
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package com.kanyun.kudos.json.reader

import android.util.JsonReader
import com.kanyun.kudos.json.reader.adapter.KudosJsonAdapter
import com.kanyun.kudos.json.reader.adapter.parseKudosObject
import java.lang.reflect.Type

object KudosAndroidJsonReader {
inline fun <reified T> fromJson(json: String): T {
Expand All @@ -33,4 +35,9 @@ object KudosAndroidJsonReader {
throw IllegalArgumentException("class ${clazz.name} must implement KudosJsonAdapter")
}
}

fun <T> fromJson(json: String, type: Type): T {
val jsonReader = JsonReader(json.reader())
return parseKudosObject(jsonReader, type) as T
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,26 @@
package com.kanyun.kudos.json.reader.adapter

import android.util.JsonReader
import android.util.JsonToken
import com.kanyun.kudos.collections.KudosCollection
import com.kanyun.kudos.collections.KudosList
import com.kanyun.kudos.collections.KudosSet
import java.lang.reflect.Type

interface KudosJsonAdapter<T> {
fun fromJson(jsonReader: JsonReader): T
}

fun parseKudosObject(jsonReader: JsonReader, type: Type): Any {
fun parseKudosObject(jsonReader: JsonReader, type: Type): Any? {
return if (type is ParameterizedTypeImpl) {
parseKudosObjectInternal(jsonReader, type.rawType, type.actualTypeArguments)
} else {
parseKudosObjectInternal(jsonReader, type, arrayOf())
}
}

private fun parseKudosList(jsonReader: JsonReader, typeArguments: Array<Type>): List<Any> {
val list = mutableListOf<Any>()
private fun parseKudosList(jsonReader: JsonReader, typeArguments: Array<Type>): List<Any?> {
val list = mutableListOf<Any?>()
jsonReader.beginArray()
while (jsonReader.hasNext()) {
list.add(parseKudosObject(jsonReader, typeArguments[0]))
Expand All @@ -41,6 +45,19 @@ private fun parseKudosList(jsonReader: JsonReader, typeArguments: Array<Type>):
return list
}

private fun parseKudosCollection(jsonReader: JsonReader, type: Type, typeArguments: Array<Type>): KudosCollection<Any> {
val list = KudosList<Any>()
jsonReader.beginArray()
while (jsonReader.hasNext()) {
if (jsonReader.peek() == JsonToken.NULL) {
throw NullPointerException("Element cannot be null for ${type.typeName}.")
}
list.add(parseKudosObject(jsonReader, typeArguments[0])!!)
}
jsonReader.endArray()
return list
}

private fun parseKudosArray(jsonReader: JsonReader, typeArguments: Array<Type>): Any {
val list = parseKudosList(jsonReader, typeArguments)
val array = java.lang.reflect.Array.newInstance(typeArguments[0] as Class<*>, list.size)
Expand All @@ -50,8 +67,8 @@ private fun parseKudosArray(jsonReader: JsonReader, typeArguments: Array<Type>):
return array
}

private fun parseKudosMap(jsonReader: JsonReader, typeArguments: Array<Type>): Map<String, Any> {
val resultMap = mutableMapOf<String, Any>()
private fun parseKudosMap(jsonReader: JsonReader, typeArguments: Array<Type>): Map<String, Any?> {
val resultMap = mutableMapOf<String, Any?>()
jsonReader.beginObject()
while (jsonReader.hasNext()) {
val key = jsonReader.nextName()
Expand All @@ -66,7 +83,11 @@ private fun parseKudosObjectInternal(
jsonReader: JsonReader,
type: Type,
typeArguments: Array<Type>,
): Any {
): Any? {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.skipValue()
return null
}
val value = when (type) {
String::class.javaObjectType -> jsonReader.nextString()
Int::class.javaObjectType -> jsonReader.nextInt()
Expand All @@ -77,6 +98,9 @@ private fun parseKudosObjectInternal(
List::class.javaObjectType -> parseKudosList(jsonReader, typeArguments)
Set::class.javaObjectType -> parseKudosList(jsonReader, typeArguments).toSet()
Map::class.javaObjectType -> parseKudosMap(jsonReader, typeArguments)
KudosList::class.javaObjectType -> parseKudosCollection(jsonReader, type, typeArguments)
KudosSet::class.javaObjectType -> parseKudosCollection(jsonReader, type, typeArguments).toCollection(KudosSet())
KudosCollection::class.javaObjectType -> parseKudosCollection(jsonReader, type, typeArguments)
else -> {
parseKudosObjectSpecial(jsonReader, type, typeArguments)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package com.kanyun.kudos.compiler

import com.kanyun.kudos.compiler.KudosNames.JSON_READER_PEEK_CALLABLE_ID
import com.kanyun.kudos.compiler.KudosNames.JSON_READER_SKIP_VALUE_CALLABLE_ID
import com.kanyun.kudos.compiler.KudosNames.JSON_TOKEN_CLASS_ID
import com.kanyun.kudos.compiler.KudosNames.JSON_TOKEN_NULL_IDENTIFIER
import com.kanyun.kudos.compiler.KudosNames.KUDOS_JSON_ADAPTER_CLASS_ID
import com.kanyun.kudos.compiler.utils.irThis
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
Expand All @@ -25,10 +29,12 @@ import org.jetbrains.kotlin.ir.builders.Scope
import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.builders.irBranch
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irContinue
import org.jetbrains.kotlin.ir.builders.irElseBranch
import org.jetbrains.kotlin.ir.builders.irEquals
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irGetField
import org.jetbrains.kotlin.ir.builders.irIfThen
import org.jetbrains.kotlin.ir.builders.irNotEquals
import org.jetbrains.kotlin.ir.builders.irNull
import org.jetbrains.kotlin.ir.builders.irReturn
Expand All @@ -39,11 +45,13 @@ import org.jetbrains.kotlin.ir.builders.irVararg
import org.jetbrains.kotlin.ir.builders.irWhen
import org.jetbrains.kotlin.ir.builders.irWhile
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrEnumEntry
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrBranch
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrGetEnumValueImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
Expand Down Expand Up @@ -106,14 +114,39 @@ internal class KudosFromJsonFunctionBuilder(
dispatchReceiver = irGet(jsonReader)
},
)
val jsonReaderPeekExpression = irCall(pluginContext.referenceFunctions(JSON_READER_PEEK_CALLABLE_ID).first()).apply {
dispatchReceiver = irGet(jsonReader)
}
val jsonTokenClass = pluginContext.referenceClass(JSON_TOKEN_CLASS_ID)!!
val jsonTokenNullEntry = jsonTokenClass.owner.declarations.filterIsInstance<IrEnumEntry>().first {
it.name == JSON_TOKEN_NULL_IDENTIFIER
}
val jsonTokenNullExpression = IrGetEnumValueImpl(
startOffset,
endOffset,
jsonTokenClass.defaultType,
jsonTokenNullEntry.symbol,
)
+irIfThen(
context.irBuiltIns.unitType,
irEquals(jsonReaderPeekExpression, jsonTokenNullExpression),
irBlock {
+irCall(
pluginContext.referenceFunctions(JSON_READER_SKIP_VALUE_CALLABLE_ID).first(),
).apply {
dispatchReceiver = irGet(jsonReader)
}
+irContinue(this@apply)
},
)
val branches = ArrayList<IrBranch>()
fields.forEach { field ->
branches.add(
irBranch(
irEquals(irGet(name), irString(field.name.asString())),
irBlock {
+irSetField(irFunction.irThis(), field, getNextValue(field))
if (kudosStatusField!=null) {
if (kudosStatusField != null) {
+irCall(
pluginContext.referenceFunctions(
CallableId(FqName("java.util"), FqName("Map"), Name.identifier("put")),
Expand All @@ -124,17 +157,13 @@ internal class KudosFromJsonFunctionBuilder(
dispatchReceiver = irGetField(irFunction.irThis(), kudosStatusField)
}
}
}
},
),
)
}
branches.add(
irElseBranch(
irCall(
pluginContext.referenceFunctions(
CallableId(FqName("android.util"), FqName("JsonReader"), Name.identifier("skipValue")),
).first(),
).apply {
irCall(pluginContext.referenceFunctions(JSON_READER_SKIP_VALUE_CALLABLE_ID).first()).apply {
dispatchReceiver = irGet(jsonReader)
},
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ class KudosIrClassTransformer(

private fun needsNoargConstructor(declaration: IrClass): Boolean =
declaration.kind == ClassKind.CLASS &&
declaration.hasKudosAnnotation() &&
declaration.constructors.none { it.isZeroParameterConstructor() }
declaration.hasKudosAnnotation() &&
declaration.constructors.none { it.isZeroParameterConstructor() }

// Returns true if this constructor is callable with no arguments by JVM rules, i.e. will have descriptor `()V`.
private fun IrConstructor.isZeroParameterConstructor(): Boolean {
Expand All @@ -371,21 +371,21 @@ class KudosIrClassTransformer(
if (irClass.hasKudosAnnotation()) {
val fieldType = context.irBuiltIns.mapClass.typeWith(
context.irBuiltIns.stringClass.defaultType,
context.irBuiltIns.booleanClass.defaultType
context.irBuiltIns.booleanClass.defaultType,
)
val initExpression = context.referenceFunctions(
CallableId(FqName("kotlin.collections"), Name.identifier("hashMapOf")),
).first()
val kudosStatusField = if (validatorFunction != null) {
irClass.addField(
KUDOS_FIELD_STATUS_MAP_IDENTIFIER,
fieldType
fieldType,
).apply {
initializer = DeclarationIrBuilder(
context,
symbol,
symbol.owner.startOffset,
symbol.owner.endOffset
symbol.owner.endOffset,
).run {
irExprBody(irCall(initExpression))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.kanyun.kudos.compiler

import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
Expand All @@ -41,11 +42,18 @@ object KudosNames {
val KUDOS_VALIDATOR_CLASS_ID = ClassId(FqName("com.kanyun.kudos.validator"), Name.identifier("KudosValidator"))
val KUDOS_JSON_ADAPTER_CLASS_ID = ClassId(FqName("com.kanyun.kudos.json.reader.adapter"), Name.identifier("KudosJsonAdapter"))
val JSON_READER_CLASS_ID = ClassId.fromString("android/util/JsonReader")
val JSON_TOKEN_CLASS_ID = ClassId(FqName("android.util"), Name.identifier("JsonToken"))

// CallableId
val JSON_READER_SKIP_VALUE_CALLABLE_ID = CallableId(FqName("android.util"), FqName("JsonReader"), Name.identifier("skipValue"))
val JSON_READER_PEEK_CALLABLE_ID = CallableId(FqName("android.util"), FqName("JsonReader"), Name.identifier("peek"))
val JSON_TOKEN_NULL_CALLABLE_ID = CallableId(FqName("android.util"), FqName("JsonToken"), Name.identifier("NULL"))

// Name.identifier
val KUDOS_FROM_JSON_IDENTIFIER = Name.identifier("fromJson")
val JSON_READER_IDENTIFIER = Name.identifier("jsonReader")
val KUDOS_FIELD_STATUS_MAP_IDENTIFIER = Name.identifier("kudosFieldStatusMap")
val JSON_TOKEN_NULL_IDENTIFIER = Name.identifier("NULL")

val CONTAINER_FQ_NAMES = setOf(
"kotlin.Array",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class FromJsonFunctionDescriptorImpl(
emptyList(),
valueParameters,
classDescriptor.defaultType,
Modality.FINAL,
Modality.OPEN,
DescriptorVisibilities.PUBLIC,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,56 @@ import org.junit.Test
* Created by Benny Huo
*/
class KudosTests : TestBase() {

@Test
fun `common_classDeclarationCheck`() = testBase()

@Test
fun `common_constructor`() = testBase()

@Test
fun `common_defaultValue`() = testBase()

@Test
fun `common_initBlock`() = testBase()


@Test
fun `common_notNull`() = testBase()

@Test
fun `common_propertyTypeCheck`() = testBase()


@Test
fun `common_validator`() = testBase()

@Test
fun `gson_jsonAdapterCheck`() = testBase()

@Test
fun `gson_notNull`() = testBase()

@Test
fun `gson_validator`() = testBase()

fun `jackson_notNull`() = testBase()
@Test
fun `jsonReader_deserialize`() = testBase()

@Test
fun `jsonReader_deserializeArrayType`() = testBase()

@Test
fun `jsonReader_deserializeFloatType`() = testBase()

@Test
fun `jsonReader_deserializeMapType`() = testBase()

@Test
fun `jsonReader_deserializeSetType`() = testBase()


@Test
fun `jsonReader_notNull`() = testBase()

@Test
fun `jsonReader_simple`() = testBase()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ open class TestBase {
import com.google.gson.annotations.JsonAdapter
import com.kanyun.kudos.gson.kudosGson
import com.kanyun.kudos.gson.adapter.KudosReflectiveTypeAdapterFactory
import java.lang.reflect.Type
inline fun <reified T: Any> deserialize(string: String): T? {
inline fun <reified T: Any> deserialize(string: String, type: Type = T::class.java): T? {
val gson = kudosGson()
return try {
val t: T = gson.fromJson(string, object: TypeToken<T>() {}.type)
Expand All @@ -89,8 +90,9 @@ open class TestBase {
import com.kanyun.kudos.jackson.kudosObjectMapper
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.DeserializationFeature
import java.lang.reflect.Type
inline fun <reified T: Any> deserialize(string: String): T? {
inline fun <reified T: Any> deserialize(string: String, type: Type = T::class.java): T? {
val mapper = kudosObjectMapper()
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.disable(DeserializationFeature.WRAP_EXCEPTIONS);
Expand All @@ -110,10 +112,11 @@ open class TestBase {
return """
// FILE: JsonReader.kt
import com.kanyun.kudos.json.reader.KudosAndroidJsonReader
import java.lang.reflect.Type
inline fun <reified T: Any> deserialize(string: String): T? {
inline fun <reified T: Any> deserialize(string: String, type: Type = T::class.java): T? {
return try {
val t: T = KudosAndroidJsonReader.fromJson(string)
val t: T = KudosAndroidJsonReader.fromJson(string, type)
println(t)
t
} catch (e: Exception) {
Expand Down
Loading

0 comments on commit 6393d65

Please sign in to comment.