I have data class like this
#Tagme("Response")
#TagResponse
data class Response(
val id: Int,
val title: String,
val label: String,
val images: List<String>,
#Input(Parameter::class)
val slug: Slug
)
Using annotation processor, I was able to get Response properties using this approach:
val parentMetadata = (element as TypeElement).toImmutableKmClass()
parentMetadata.constructors[0].valueParameters.map { prop ->
// this will loop through class properties and return ImmutableKmValueParameter
// eg: id, title, label, images, slug.
// then, I need to get the annotation that property has here.
// eg: #Input(Parameter::class) on slug property.
// if prop is slug, this will return true.
val isAnnotationAvailable = prop.hasAnnotations
// Here I need to get the annotated value
// val annotation = [..something..].getAnnotation(Input::class)
// How to get the annotation? So I can do this:
if ([prop annotation] has [#Input]) {
do the something.
}
}
Previously I tried to get the annotation like this:
val annotations = prop.type?.annotations
But, I got empty list even isAnnotationAvailable value is true
Thanks in advance!
Annotations are only stored in metadata if they have nowhere else they can be stored. For parameters, you must read them directly off of the Parameter (reflection) or VariableElement (elements API). This is why we have the ClassInspector API. You almost never want to try to read anything other than basic class data. Anything that's already contained in the bytecode or elements is basically never duplicated into metadata as well. Treat metadata as added signal, not a wholesale replacement.
Related
Here's my scenario:
I have a deep compositional tree of POJOs from various classes. I need to write a utility that can dynamically process this tree without having a baked in understanding of the class/composition structure
Some properties in my POJOs are annotated with a custom annotation #PIIData("phone-number") that declares that the property may contain PII, and optionally what kind of PII (e.g. phone number)
As a byproduct of serializing the root object, I'd like to accumulate a registry of PII locations based on their JSON path
Desired data structure:
path
type
household.primaryEmail
email-address
household.members[0].cellNumber
phone-number
household.members[0].firstName
first-name
household.members[1].cellNumber
phone-number
I don't care about the specific pathing/location language used (JSON Pointer, Json Path).
I could achieve this with some reflection and maintenance of my own path, but it feels like something I should be able to do with Jackson since it's already doing the traversal. I'm pretty sure that using Jackson's attributes feature is the right way to attach my object that will accumulate the data structure. However, I can't figure out a way to get at the path at runtime. Here's my current Scala attempt (hackily?) built on top of a filter that is applied to all objects through a mixin:
object Test {
#JsonFilter("pii")
class PiiMixin {
}
class PiiAccumulator {
val state = mutable.ArrayBuffer[String]()
def accumulate(test: String): Unit = state += test
}
def main(args: Array[String]): Unit = {
val filter = new SimpleBeanPropertyFilter() {
override def serializeAsField(pojo: Any, jgen: JsonGenerator, provider: SerializerProvider, writer: PropertyWriter): Unit = {
if (writer.getAnnotation(classOf[PiiData]) != null) {
provider.getAttribute("pii-accumulator").asInstanceOf[PiiAccumulator].accumulate(writer.getFullName.toString)
}
super.serializeAsField(pojo, jgen, provider, writer)
}
override def include(writer: BeanPropertyWriter): Boolean = true
override def include(writer: PropertyWriter): Boolean = true
}
val provider = new SimpleFilterProvider().addFilter("pii", filter)
val mapper = new ObjectMapper()
mapper.addMixIn(classOf[Object], classOf[PiiMixin])
val accum = new PiiAccumulator()
mapper.writer(provider)
.withAttributes("pii-accumulator", accum)
.writeValueAsString(null) // Pass in any arbitrary object here
}
}
This code has enabled me to dynamically buffer up a list of property names that contain PII, but I can't figure out how to get their locations within the resulting JSON doc. Perhaps the Jackson architecture somehow precludes knowing that at runtime. Is there some other place I can hook in to do something like this, perhaps while converting to a JsonNode?
Thanks!
Okay, found it. You can access the recursive path/location during serialization via JsonGenerator.getOutputContext.pathAsPointer(). So by changing my code above to the following:
if (writer.getAnnotation(classOf[PIIData]) != null) {
provider.getAttribute("pii").asInstanceOf[PiiAccumulator]
.accumulate(jgen.getOutputContext.pathAsPointer().toString + "/" + writer.getName)
}
I'm able to dynamically buffer a list of special locations in the resulting JSON document for further dynamic processing.
I am developing a simple Android app, that will display an icon of a vehicle and the user can click on the icon to display the vehicle information. I want to load the data dynamically when I build the app i.e. the data will come from an external source including the picture for the icon.
I am new to Kotlin and not sure what to search for to understand a suitable solution. What is the correct way to define the data, is it best to create an class as below then create an array of the class (not sure if this is possible)
public class VehicleSpec()
{
var OEM: String? = null
var ModelName: String? = null
var EngineSize: String? = null
}
Or would be better to create a multiple dimension array and then link the data to the cells?
var VehicleSpec = arrayOf(20,20)
VehicleSpec[0][0] = Null //OEM
VehicleSpec[0][1] = Null //ModelName
VehicleSpec[0][2] = Null //EngineSize
What is the best way to set up the data storage, is there any good references to understand how this should be setup?
What is the correct way to define the data, is it best to create an class as below then create an array of the class
Using an array for the properties of an object is not making the full use of the type safety you have in Kotlin (and even Java for that matter).
If what you want to express is multiple properties of an object, then you should use a class to define those properties. This is especially true if the properties have different types.
There is no performance difference between an array and a class, because you'll get a reference to the heap in both cases. You could save on performance only if you convert your multi-dimensional array approach to a single-dimension array with smart indexing. Most of the time, you should not consider this option unless you are handling a lot of data and if you know that performance is an issue at this specific level.
(not sure if this is possible)
Defining lists/arrays of classes is definitely possible.
Usually, for classes that are only used as data containers, you should prefer data classes, because they give you useful methods for free, and these methods totally make sense for simple "data bags" like in your case (equals, hashcode, component access, etc.).
data class Vehicle(
val OEM: String,
val ModelName: String,
val EngineSize: String
)
Also, I suggest using val instead of var as much as possible. Immutability is more idiomatic in Kotlin.
Last but not least, prefer non-null values to null values if you know a value must always be present. If there are valid cases where the value is absent, you should use null instead of a placeholder value like empty string or -1.
First at all, using the "class aprocah" makes it easy for you to understand and give you the full benefits of the language itself... so dont dry to save data in an array .. let the compiler handle those stuff.
Secondly i suggest you have maybe two types (and use data classes ;-) )
data class VehicleListEntry(
val id: Long,
val name: String
)
and
data class VehicleSpec(
val id: Long,
val oem: String = "",
val modelName: String = "",
val engineSize: String = ""
)
from my perspective try to avoid null values whenever possible.
So if you have strings - which you are display only - use empty strings instead of null.
and now have a Model to store your data
class VehicleModel() {
private val specs: MutableMap<Long, VehicleSpec> = mutableMapOf()
private var entries: List<VehicleListEntry> = listOf()
fun getSpec(id: Long) = specs[id]
fun addSpec(spec: VehicleSpec) = specs[spec.id] = spec
fun getEntries(): List<VehicleListEntry> = entries
fun setEntries(data: List<VehicleListEntry>) {
entries = data.toMutableList()
}
}
You could also use a data class for your model which looks like
data class VehicleModel(
val specs: MutableMap<Long, VehicleSpec> = mutableMapOf(),
var entries: List<VehicleListEntry> = listOf()
)
And last but not least a controller for getting stuff together
class VehicleController() {
private val model = VehicleModel()
init{
// TODO get the entries list together
}
fun getEntries() = model.entries
fun getSpec(id: Long) : VehicleSpec? {
// TODO load the data from external source (or check the model first)
// TODO store the data into the model
// TODO return result
}
}
My goal: I have a simple class with a public
val reds = IntArray(10)
val greens = IntArray(10)
val blues = IntArray(10)
val lums = IntArray(10)
If someone modifies any red value, I'd like to update the lum value.
myObj.reds[5] = 100 // Should update myObj.lums[5] = reds[5]+greens[5]+blues[5]
The problems is that the by Delegates.observable seem to only be used for var objects - nothing mentions "and if you modify an element of an array, here is what gets triggered"
Maybe this isn't possible and I have to do all modifications through getters and setters - but I'd much rather have something trigger like an observable!
You will have to use a custom class instead, IntArray is mapped to primitive int[] array so it doesn't provide any place to inject callback - changing value like your example (myObj.reds[5] = 100) you only know when array is returned, but have no control over changes after that.
For example you can create class like this:
class IntArrayWrapper(size: Int,
val setterObs : ((index: Int, value: Int) -> Unit)? = null){
val field = IntArray(size)
val size
get() = field.size
operator fun iterator() = field.iterator()
operator fun get(i: Int) : Int {
return field[i]
}
operator fun set(i: Int, value: Int){
field[i] = value
setterObs?.invoke(i, value)
}
}
Operator functions will let you get values from underlying array with same syntax as if you were accessing it directly. setterObs argument in constructor lets you pass the "observer" for setter method:
val reds = IntArrayWrapper(10){index, value ->
println("$index changed to $value")
invalidateLums(index) // method that modifies lums or whatever you need
}
val a = reds[2] // getter usage
reds[3] = 5 // setter usage that triggers the setter observer callback
reds.field[4] = 3 // set value in backing array directly, allows modification without setter callback
Note that this imposes limitations, as you won't be able to freely use IntArray extension methods without referencing backing field nor will you be able to pass this class as an Array argument.
I do not know if it is the cleanest way of solving your problem but, you could use the ObservableList (FX observable collections):
var numbers: ObservableList<Int> = FXCollections.observableArrayList()
numbers.addListener(ListChangeListener<Int> {
//Do things on change
})
But as I mentioned, by adding these collections you are mixing FX components into your application, which I do not know if it is wanted or even if it works on various platforms like android!
i'm new in kotlin and i want to know if we can transform a content value at initialisation : with this example :
#Document
data class Category(
#Id val id: Id? = null,
val label: String
)
Category is a document (entity for mongodb) and when i'm instanciating this object, i want to transform label property in uppercase. How can i do that to stay idiomatic with the language ? The point is to keep the immutable properties of the val keyword.
val categ = Category(label = "Test")
println(categ.label) // --> TEST
Thanks.
You can encapsulate the "upperCasing" into a factory:
data class Category constructor(val label: String) {
init {
if (label != label.toUpperCase()) {
throw IllegalStateException("Label must be uppercase")
}
}
companion object {
fun createInstance(str: String) = Category(str.toUpperCase())
}
}
The init block ensures, that clients don't create unwanted instances with non-upper labels (which should be documented).
Create an instance like this:
val instance = Category.createInstance("xy")
You might want to make explicit that you do transformations if the parameter is not upper case already by naming the factory accordingly, e.g. withTransformedLabel or simply add some documentation ;-)
I have received a JavaScript object in response to a remote HTTP request. I have a kotlin model (trait) that defines the various fields I expect on the object (the nullable ones are optional).
First, I want to do an is check to make sure my object is in fact of the expected type. I initially tried payload is MyModel but that doesn't work due to the way the is operator is written in kotlin.js.
Second, I want to cast to MyModel so I can get auto-complete, etc. on the object while I work with it. Normally, the is alone would be enough but since that doesn't work I need something for this problem as well.
I would like to avoid manually populating my object from a dynamic. I wouldn't mind doing this so much if I could use by Delegates.mapVal(...) but that requires a Map<String, Any?> and I don't know how to get my dynamic/Any? payload into a Map<String, Any?>.
1) We don't have structure check for is in performance reasons.
I don't sure that we need generic solution for this, but anyway I created issue about it, feel free to vote or star it to get updates.
2) is enough if you use smart cast, like:
if (payload is MyModel) {
// call MyModel members on payload
}
But don't forget about (1) :)
3) You can write something like:
class MapDynamic<out V>(val d: dynamic) {
public fun get(thisRef: Any, desc: PropertyMetadata): V {
return d[desc.name]
}
}
class Foo(data: dynamic) {
val field: Int by MapDynamic(data)
}
fun main(args : Array<String>) {
val f = Foo(object { val field = 123 })
println(f.field)
}
But it looks too verbose, but You can add additional logic for e.g. when data don't have requested field. And if You don't need custom logic I think cast is enough.
For the second part, the cast, you can do:
fun responseHandler(payload: dynamic) {
val myModel = payload as MyModel
}
or
fun responseHandler(payload: dynamic) {
val myModel: MyModel = payload
}
This will throw an NPE if payload is null, but it won't actually validate that the payload matches MyModel. In particular, you may end up with null fields/properties that shouldn't be if the payload was missing those fields/properties.