Last active
February 8, 2018 19:08
-
-
Save emrul/2d446eff2084313fcb332f07e62b2ae8 to your computer and use it in GitHub Desktop.
Hack to get Jsoniter to recognise JSONProperty annotations on Kotlin data classes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data class User(@JsonProperty("userName") val name: String) | |
val userObj = User("John") | |
JsoniterKotlinSupport.enable() | |
JsoniterAnnotationSupport.enable() | |
val jsonUserString = JsonStream.serialize(userObj) | |
// jsonUserString will contain: {"userName":"John"} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.jsoniter.annotation.JsonProperty | |
import com.jsoniter.spi.* | |
import kotlin.reflect.full.findAnnotation | |
import kotlin.reflect.jvm.kotlinFunction | |
private val metadataFqName = "kotlin.Metadata" | |
fun Class<*>.isKotlinClass(): Boolean { | |
return this.declaredAnnotations.singleOrNull { it.annotationClass.java.name == metadataFqName } != null | |
} | |
class JsoniterKotlinSupport : EmptyExtension() { | |
companion object { | |
private var enabled = false | |
fun enable() { | |
if (enabled) { | |
return | |
} | |
enabled = true | |
JsoniterSpi.registerExtension(JsoniterKotlinSupport()) | |
} | |
} | |
override fun updateClassDescriptor(desc: ClassDescriptor?) { | |
if ( desc?.clazz?.isKotlinClass() != true ) return | |
detectKotlinDataClass(desc) | |
} | |
fun detectKotlinDataClass(desc: ClassDescriptor) { | |
// Only interested in data classes | |
if ( !desc.clazz.kotlin.isData ) return | |
// Clear getters in the descriptor since we will access via field | |
desc.getters?.clear() | |
// Look through each declared constructor | |
desc.clazz.declaredConstructors.forEach { constructor -> | |
// See if there is a parameter with a JSONProperty annotation | |
val hasJsonAnnotations = constructor.kotlinFunction?.parameters?.any { parameter -> (parameter.findAnnotation<JsonProperty>() != null) } ?: false | |
if ( hasJsonAnnotations ) { | |
// Populate the constructor | |
desc.ctor = getCtor(desc.clazz) | |
desc.ctor.ctor = constructor | |
desc.ctor.staticMethodName = null | |
desc.ctor.staticFactory = null | |
// Go through each constructor parameter and see if it has a JSONProperty annotation | |
constructor.kotlinFunction?.parameters?.forEachIndexed { i, parameter -> | |
val jsonProperty = getJsonProperty(parameter.annotations.toTypedArray()) // parameter.findAnnotation<JsonProperty>() | |
val field = desc.fields.find { field -> field.name == parameter.name } | |
// If we have a field with the same name as the parameter then set or add the JSONProperty annotation to it | |
if ( field != null && jsonProperty != null ) { | |
if ( field.annotations == null ) { | |
field.annotations = arrayOf(jsonProperty) | |
} | |
else { | |
field.annotations = field.annotations + jsonProperty | |
} | |
} | |
// Deserialisation support | |
val binding = Binding(desc.clazz, desc.lookup, constructor.genericParameterTypes[i]) | |
if (jsonProperty != null) { | |
updateBindingWithJsonProperty(binding, jsonProperty) | |
} | |
if (binding.name == null || binding.name.isEmpty()) { | |
binding.name = parameter.name | |
} | |
binding.annotations = parameter.annotations.toTypedArray() | |
desc.ctor.parameters.add(binding) | |
} | |
} | |
} | |
} | |
private fun getCtor(clazz: Class<*>): ConstructorDescriptor { | |
val cctor = ConstructorDescriptor() | |
try { | |
cctor.ctor = clazz.getDeclaredConstructor() | |
} catch (e: Exception) { | |
cctor.ctor = null | |
} | |
return cctor | |
} | |
private fun getJsonProperty(annotations: Array<Annotation>): JsonProperty? { | |
return getAnnotation(annotations, JsonProperty::class.java) | |
} | |
private fun <T : Annotation> getAnnotation(annotations: Array<Annotation>?, annotationClass: Class<T>): T? { | |
if (annotations == null) { | |
return null | |
} | |
return annotations | |
.firstOrNull { annotationClass.isAssignableFrom(it.javaClass) } | |
?.let { | |
@Suppress("UNCHECKED_CAST") | |
it as T | |
} | |
} | |
@Throws(Exception::class) | |
private fun reflectCall(obj: Any, methodName: String, vararg args: Any): Any { | |
val method = obj.javaClass.getMethod(methodName) | |
return method.invoke(obj, *args) | |
} | |
private fun updateBindingWithJsonProperty(binding: Binding, jsonProperty: JsonProperty) { | |
binding.asMissingWhenNotPresent = jsonProperty.required | |
binding.isNullable = jsonProperty.nullable | |
binding.isCollectionValueNullable = jsonProperty.collectionValueNullable | |
binding.shouldOmitNull = jsonProperty.omitNull | |
val altName = jsonProperty.value | |
if (!altName.isEmpty()) { | |
binding.name = altName | |
binding.fromNames = arrayOf(altName) | |
} | |
if (jsonProperty.from.isNotEmpty()) { | |
binding.fromNames = jsonProperty.from | |
} | |
if (jsonProperty.to.isNotEmpty()) { | |
binding.toNames = jsonProperty.to | |
} | |
val decoder = jsonProperty.decoder | |
if (decoder.java != Decoder::class.java) { | |
try { | |
binding.decoder = decoder.java.newInstance() | |
} catch (e: Exception) { | |
throw JsonException(e) | |
} | |
} | |
val encoder = jsonProperty.encoder | |
if (encoder.java != Encoder::class.java) { | |
try { | |
binding.encoder = encoder.java.newInstance() | |
} catch (e: Exception) { | |
throw JsonException(e) | |
} | |
} | |
val implementation = jsonProperty.implementation | |
if (implementation.java != Any::class.java) { | |
binding.valueType = ParameterizedTypeImpl.useImpl(binding.valueType, jsonProperty.implementation::class.java) | |
binding.valueTypeLiteral = TypeLiteral.create(binding.valueType) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment