Moshi: PolymorphicJsonAdapterFactory is it possible to get the type in withDefaultValue? - moshi

I have a moshi PolymorphicJsonAdapterFactory and it works great.
.withSubtype(ColdWeather::class.java, "Cold")
.withSubtype(HotWeather::class.java, "Hot")
.withDefaultValue(//how to grab the label)
The method withDefaultValue is a great catch all, but my BE team wants me to log the actual label that comes down in order to help catch a bug that's going on on their end. As far as I can tell... in the withDefaultValue I can't grab a reference to the label which in this case the backend is sending back "Medium".
I feel like there must be a way to grab this label (but I'm missing something simple?) so I can log it and possibly propagate it in the withDefaultValue method.

I stumbled on the issue a while ago. I found it impossible to achieve with just using .withDefaultValue method. So far I did not find better solution other than .withFallbackJsonAdapter (I am using moshi version 1.12), which lets you parse the json manually in case the label is unknown to your PolymorphicJsonAdapterFactory adapter. The documentation says:
/**
* Returns a new factory that with default to {#code fallbackJsonAdapter.fromJson(reader)} upon
* decoding of unrecognized labels.
*
* <p>The {#link JsonReader} instance will not be automatically consumed, so make sure to consume
* it within your implementation of {#link JsonAdapter#fromJson(JsonReader)}
*/
public PolymorphicJsonAdapterFactory<T> withFallbackJsonAdapter(
#Nullable JsonAdapter<Object> fallbackJsonAdapter) {
return ...
}
I assume your code is somewhat like this (simplified):
interface Weather {
val type: String
}
#JsonClass(generateAdapter = true)
class ColdWeather( #Json(name = "type") override val type: String) : Weather
#JsonClass(generateAdapter = true)
class HotWeather( #Json(name = "type") override val type: String) : Weather
val weatherAdapter = PolymorphicJsonAdapterFactory.of(Weather::class.java, "type")
.withSubtype(ColdWeather::class.java, "Cold")
.withSubtype(HotWeather::class.java, "Hot")
and you receive a json similar to this:
{
"weather" : {
"type" : "Cold"
}
}
To receive an unknown label, I would do something like this:
class UnknownWeather(override val type: String) : Weather
val weatherAdapter = PolymorphicJsonAdapterFactory.of(Weather::class.java, "type")
.withSubtype(ColdWeather::class.java, "Cold")
.withSubtype(HotWeather::class.java, "Hot")
.withFallbackJsonAdapter((object : JsonAdapter<Any>() {
override fun fromJson(reader: JsonReader): UnknownWeather {
var type = ... // parse it from the reader
return UnknownWeather(type)
}
override fun toJson(writer: JsonWriter, value: Any?) {
// nothing to do
}
}))
Of course that means that you will have to dig a bit into JsonReader, but it has a fairly understandable interface, you basically iterate through the properties of the json object and extract what you need, in our case just the "type" property.
FYI, seems like more people had problem with this: https://github.com/square/moshi/issues/784

Related

Spring cache for specific values #Cacheable annotation

I want to cache a result of a method only when the attribute of the result contains specific values. For example
Class APIOutput(code: Int, message: String)
sealed class Response<out T : Any> : Serializable {
data class Success<out T : Any>(val data: T) : Response<T>()
data class Error(val errorText: String, val errorCode: Int) : Response<Nothing>()
}
#Cacheable(
key = "api-key",
unless = "do something here"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
In the above method, I want to cache the response only when Response.Success.data.code == (long list of codes).
Please note, in the previous line data is nothing but APIOutput object. How could I achieve it using unless or any other approach. I was thinking of writing a function that takes a doApicall method result as input and would return true or false and call that method it as unless="call a method". But I'm not sure how to do it. Any help is highly appreciated.
You can specify an expression to be evaluated in unless using SpEL. The returned value is available as result so you can do something like -
#Cacheable(
key = "api-key",
unless = "#result!=null or #result.success.data.code!=200"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
You can even use Regex in SpEL and can create custom Expression parsers if the existing functionality is not enough for your usecase.
Thanks Yatharth and John! Below is the condition that worked for me. resultcodes in the below expression is a list
#Cacheable(
key = "api-key",
unless = "!(#result instanceof T(com.abc.Response\$Success))
or (#result instanceof T(com.abc.Response\$Success)
and !(T(com.abc.APIStatus).resultCodes.contains(#result.data.code)))"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}

Polymorphic #RequestBody in Spring-Boot

The problem's pretty straightforward. I have a couple of events that derive from the same interface, and I'd like to deserialize them to their propper super-class.
I know how to do that with an object mapper, but using my own mapper would mean letting Spring-Boot parse the #RequestBody as a String and then doing it myself, which isn't the worlds end, but I can't help but suspect that Spring provides proper tools to handle this kind of situation. Trouble is, I can't seem to find them.
Here's a bit of sample code:
example event:
interface YellowOpsEvent {
val user: String
val partner: String
val subject: String
val change: NatureOfChange
}
data class StatusChangedEvent(override val user: String,
override val partner: String,
override val subject: String,
val before: String,
val after: String): YellowOpsEvent {
override val change = NatureOfChange.Changed
}
controller:
#PostMapping("/event")
fun writeEvent(#RequestBody event: YellowOpsEvent) { // < I expect this not to throw an exception
val bugme = event is StatusChangedEvent // < I expect this to return true if I send the proper event data.
}
Just to clarify, I perfectly understand why this doesn't work out of the box. The trouble is, I can't find out what I need to do to make it work.
The link in pL4Gu33's comment lead me in the right direction, but it took some additional searching and fiddling, plucking information from here and there to arrive at the solution that would finally work, so I'm summarising it here for completeness.
The trouble is that you'll need two annotations, one on the interface and one on the implementing classes, the combined use of which seems somewhat ill-documented.
First, on the interface, add this annotation. Contrary to some tutorials you will find, no further annotation of the interface is required:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
interface YellowOpsEvent {
val user: String
val partner: String
val subject: String
val change: NatureOfChange
}
According to some documentation, this alone should be enough for propper deserialisation. The spring-boot controller, however, will throw an exception because the passed root name does not match the class it was expecting.
// the above will throw an exception when the serialization product is sent to this controller:
#PostMapping("/event")
fun writeEvent(#RequestBody event: YellowOpsEvent) { // < I expect this not to throw an exception
val bugme = event is StatusChangedEvent // < I expect this to return true if I send the proper event data.
}
To fix that, add the #JsonRootName annotation to any implementing classes, with the interface's name. Most documentation of this annotation don't use it for this, instead just for renaming the type, and even when it's mentioned in the linked question in the context of polymorphism, it wrongly uses its own name. This is what it needs to look like:
#JsonRootName("YellowOpsEvent")
data class StatusChangedEvent(override val user: String,
override val partner: String,
override val subject: String,
val before: String,
val after: String): YellowOpsEvent {
override val change = NatureOfChange.Changed
}
Now it works! :)

How can I create Hashmap<String,Int : String,String : String,String> in Kotlin for sorting

I know this character (:) is meaningless in my statement, but I wanted to explain what I want. I want to sort a lot of hashmaps adding Arraylist and using sortedBy but I cant because my values return strings.
Here is my code:
newReference.addValueEventListener(object : ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
chatMessages.clear()
for(ds in p0.child(playerIDmatchWhoIs).children){
var hashMap = ds.getValue() as HashMap<String, String>
var datetime = hashMap.get("datetime").toString()
var usermail = hashMap.get("usermail")
var usermessage = hashMap.get("usermessage")
chatMessages.add("${usermail}: ${usermessage}")
recyclerViewAdapter.notifyDataSetChanged()
}
}
})
(I want to sort this hashMap, it has datetime value but is returning string.)
println(hashMap): I/System.out: {datetime=1574807563747, usermessage=jmjgmhg, usermail=1#gmail.com}
I assume that chatMessages is of type List<String>. This is generally bad because you cannot to anything with strings. I would suggest you to create a data class which contains all information about a chat message, like so:
data class ChatMessage(val dateTime: Int, val userMail: String?, val userMessage: String?) : Comparable<ChatMessage> {
override fun compareTo(other: ChatMessage) = this.dateTime.compareTo(other.dateTime)
}
As you can see, this class implements the Comparable<ChatMessage> interface. If you then define the chatMessages list like so
private val chatMessages = mutableListOf<ChatMessage>()
you can call chatMessages.sort() which will then sort the list according to dateTime (see the implementation of compareTo in ChatMessage). The final code would look like that:
data class ChatMessage(val dateTime:Int?, val userMail: String?, val userMessage: String?) : Comparable<ChatMessage> {
override fun compareTo(other: ChatMessage) = this.dateTime.compareTo(other.dateTime)
}
private val chatMessages = mutableListOf<ChatMessage>()
fun yourCode() {
newReference.addValueEventListener(object : ValueEventListener {
/* Use proper variable naming. Nobody will understand, what p0 is, but if you name
it dataSnapshot, everyone knows at a glance. */
override fun onDataChange(dataSnapshot: DataSnapshot) {
chatMessages.clear()
// Again, what is ds exactly? Name it properly.
for (ds in dataSnapshot.child(playerIDmatchWhoIs).children) {
// Kotlin recommends to use val instead of var.
// This way, you know that your variables cannot be modified unless you want them to be modified.
val hashMap = ds.getValue() as HashMap<String, String>
// use indexing instead of the get() method
val dateTime = hashMap["datetime"]
val userMail = hashMap["usermail"]
val userMessage = hashMap["usermessage"]
// TODO: Handle null values properly
chatMessages.add(ChatMessage(dateTime!!.toInt(), userMail, userMessage))
recyclerViewAdapter.notifyDataSetChanged()
}
chatMessages.sort()
}
})
}
This assumes that you want to store your timestamp as an integer. However, I would rather recommend to use a time library like java.time (built into java). In that case, you can use java.time.Instant which has many more possibilities to handle time and all the difficulties to handle time.
Read more about java.time.Instant in the Android docs. If you want to learn how to parse a String to java.time.Instant, this might be interesting.

Spring boot 2.1.0 security change with kotlin data class?

This problem make me physically ill.
Joke aside, I've been trying to add an authentication layer to my web app using spring-boot with security plugin. Here is my data class.
#Document(collection = "user")
data class User (
var name : String,
var password : String,
var email : String,
var type : String,
var status : String,
var balance : Int
){
#Id val id : String = ObjectId.get().toHexString()
}
After some searching, Ctr+C, Ctr+V, I'm successfully set-up some custom authentication that will get user information from database, look like this:
override fun loadUserByUsername(name : String): UserDetails {
logger.info(name)
val user = repo.findByName(name)
return User(user!!.name,passwordEncoder.encode(user.password),AuthorityUtils.NO_AUTHORITIES)
}
Here where the fun begin, its seem that the code never run pass val user = repo.findByName(name). Worst thing is, there are no exception being thrown, the code run to that line and the rest just disappear.
Out of frustration, I decide to fake the return object so that I can get pass the authentication like this:
override fun loadUserByUsername(name : String): UserDetails {
logger.info(name)
//val user = repo.findByName(name)
logger.debug("asdkfhasdklfjhasdf")
return User("string",passwordEncoder.encode("you"),AuthorityUtils.NO_AUTHORITIES)
}
Now, finally I can get some exception:
{
"timestamp": "2018-11-08T18:08:29.541+0000",
"status": 500,
"error": "Internal Server Error",
"message": "No accessor to set property #org.springframework.data.annotation.Id()private final java.lang.String com.sonnbh.jwt.User.id!",
"path": "/user"
}
The exception state that spring cannot access property id so I change the type of id from val to var.
#Document(collection = "user")
data class User (
var name : String,
var password : String,
var email : String,
var type : String,
var status : String,
var balance : Int
){
#Id var id : String = ObjectId.get().toHexString()
}
Finally, my app work as expected. However, after some attempt trying to dig deeper to the problem, I found that this problem only occur to spring-boot v2.1.0. My old project which use spring-boot v2.0.5 actually run fine with val id. This led me to some question:
Did I my old implement of data class User properly? I just want to prevent any change to User.id after its being read from database or init. What can I do to improve?
Why spring-boot v2.1 can't access to the property like spring-boot v2.0.5 did?
Spring Data in 2.1. has changed the way in which it deals with final fields in entities. It no longer uses reflection to override the immutability of the fields, which in general is good. There are a few ways to cope with the problem.
They are described here: https://jira.spring.io/browse/DATACMNS-1374?focusedCommentId=182289&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-182289
Here's what the Spring guys recommend:
Add a #PersistenceConstructor to construct the entity that sets immutable fields.
Add wither methods (MyEntity withXxx(…)) to create a new instance that contains the changed property value.
Alternatively: Use Kotlin's data class feature. This will basically do the same as wither methods.
Can only answer the first part; you could try moving the declaration of ID to be apart of the constructor? That will satisfy your requirement of only initialising when the object is created and it will still be read only.

Scala Generics - Overloaded method

Considering the given code:
val repository =
context.getBean(
Introspector.decapitalize(t.getClass.getSimpleName).replace("C", "E").concat("Repository"))
and that my repositories have a String as Serializable.
I'm trying to do the following:
repository.asInstanceOf[ElasticsearchRepository[_, String]].save(getObject(t))
This one works fine:
repository.asInstanceOf[ElasticsearchRepository[_, String]].findAll()
But I don't know how to put that above to work.
Assuming the method getObject(t) is retuning the correct object to be persisted and since it's a Spring Data Repository, there are 2 save method. One that accept a single entity and another for a list of entities and it says overloaded method value save.
What I have tried so far:
I saw in another thread to force the method with a type, something like this:
repository.asInstanceOf[ElasticsearchRepository[_, String]].save(getObject(t) : TYPE)
This is ok if I knew the type and also my method getObject should return that same type.
Here is my getObject method which I return the object itself without any specific type:
#throws[IOException]
def getObject[T](t : T) = {
objectMapper.readValue(objectMapper.writeValueAsString(t), getClazz(t))
}
So I was trying to get the type like this:
val m = Manifest.classType(getClazz(t))
type TYPE = m.type
Looks good if I force my object to this type using getObject(t) : TYPE but I don't know how to use this same type in my getObject method to be returned.
Anyway, I don't even know if this is the best approach to do this, invoking a generic repository and save a generic object.
Just to understand what I'm trying to do, I'm using a aspect to intercept a Cassandra entity to be persisted, then get it and turn into a ElasticSearch entity to save a json(thats why the getObject(t)) and replicate into ElasticSearch.
Here is the full aspect class:
#Component
#Aspect
class ElasticAop {
#Autowired val context : ApplicationContext = null
val objectMapper : ObjectMapper = new ObjectMapper()
#Pointcut("execution(* com.test.service.cassandra.*.post(..)) && args(t)")
def getPointcutPost[T](t : T) : Unit = {}
#throws[Throwable]
#Before("getPointcutPost(t)")
def elasticSaveAspect[T](joinPoint: JoinPoint, t: T) = {
val m = Manifest.classType(getClazz(t))
type TYPE = m.type
val repository =
context.getBean(
Introspector.decapitalize(t.getClass.getSimpleName).replace("C", "E").concat("Repository"))
repository.asInstanceOf[ElasticsearchRepository[_, String]].findAll()
repository.asInstanceOf[ElasticsearchRepository[_, String]].save(getObject(t))
}
#throws[ClassNotFoundException]
def getClazz[T](t : T) = {
val className = t.getClass.getName.replace("cassandra", "elastic").replace("C", "E")
Class.forName(className)
}
#throws[IOException]
def getObject[T](t : T) = {
objectMapper.readValue(objectMapper.writeValueAsString(t), getClazz(t))
}
}
EDITED
Even setting up a type return in my getObject to Address and then setting the save method as follow save(getObject(t) : Address) give me the same overloaded error.
EDITED
I just figured out it's a limitation and a possible work around is to create a factory or something like this.
Then I created a service with a saveOrUpdate method:
trait ElasticGenericService[T <: ElasticGenericKey, R <: ElasticsearchRepository[T, String]] {
var r : R = _
def saveOrUpdate(t: T) = r.save(t)
}
and now I'm getting a cast exception:
java.lang.ClassCastException: Address cannot be cast to scala.runtime.Nothing$
What i can see here:
getObject[T](t : T) returns existential type _1 and actually kills all type checks, as you choosing the class in runtime
ElasticsearchRepository[_, String].save require existential type _2 to be passed to the save method, so _1 doesn't fit
Possible solution:
repository.asInstanceOf[ElasticsearchRepository[Any, String]].save(getObject(t).asInstanceOf[Any]) //getClass will work with runtime class instead of Any, so should be fine
Another solution (saving existential type):
def getObject[T](t : T) = {
objectMapper.readValue(objectMapper.writeValueAsString(t), getClazz(t)).asInstanceOf[T]
} //assuming T is an existential - it will return same existential as you passed

Resources