Issue when serializing Map.Entry using jackson - serialization

If I try to deserialize below type stored as String:
List<Entry<String, String>> entryList;
where entryList contains:
[{"dummyKey1":"dummyValue1"}]
I get the following errors
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.util.Map$Entry, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
I get above error while running a test case in junit, but if I remove the test case, then after deploying everything runs fine :
Above error comes while running junit test case because of absence of NoArgsConstructor in Entry. So, I created a DummyEntry with NoArgsConstructor that calls Entry with arguments as null.
DummyEntry<K, V> extends SimpleEntry<K, V>
After making this change, above error didn't come but I started getting below error after changes are deployed.
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "dummyKey1", not marked as ignorable (2 known properties: "value", "key"]).
What is the reason that one way doesn't works for junit, but in production it works while other does work in junit but not in production.
Also, I noticed one additional thing: In production, Map.Entry is serialized to
{'dummyKey1':'dummyValue1'}
whereas, test case in junit serializes the same string as
{'key':'dummyKey1', 'value':'dummyValue1'}
What is the reason about this weird behavior ? How can I make this thing work for both ?

I suspect you might be encountering an issue with different serialisation strategies for Map.Entry.
In v2.5.0 (IIRC) of jackson-databind Map.Entry was supported as a 'known type'. Prior to this version, the key and value attributes of Map.Entry would appear in a serialised Map.Entry. After this version, that's no longer the case.
Here are some example test cases showing what I mean:
#Test
public void mapSerialisationPreJackson2_5_0() throws IOException {
Map<String, String> aMap = Maps.newHashMap();
aMap.put("dummyKey1", "dummyValue1");
Set<Map.Entry<String, String>> incoming = aMap.entrySet();
ObjectMapper objectMapper = new ObjectMapper();
String serialised = objectMapper.writeValueAsString(incoming);
// prints: [{"key":"dummyKey1","value":"dummyValue1"}]
System.out.println(serialised);
Set<Map.Entry<String, String>> deserialised = objectMapper.readValue(serialised, Set.class);
// prints: [{key=dummyKey1, value=dummyValue1} (just like you posted in your question) whereas for versions > 2.5.0 the serialised form is ]
System.out.println(deserialised);
}
#Test
public void mapSerialisationPostJackson2_5_0() throws IOException {
Map<String, String> aMap = Maps.newHashMap();
aMap.put("dummyKey1", "dummyValue1");
Set<Map.Entry<String, String>> incoming = aMap.entrySet();
ObjectMapper objectMapper = new ObjectMapper();
String serialised = objectMapper.writeValueAsString(incoming);
// prints: [{"dummyKey1":"dummyValue1"}]
System.out.println(serialised);
Set<Map.Entry<String, String>> deserialised = objectMapper.readValue(serialised, Set.class);
// prints: [{dummyKey1=dummyValue1}]
System.out.println(deserialised);
}
Prior to v2.5.0 a Map.Entry would be serialised to {key=dummyKey1, value=dummyValue1} (just like you posted in your question) whereas for versions > 2.5.0 the serialised form is {dummyKey1=dummyValue1}.
I think you are using a version of jackson-databind in your test context which is < 2.5.0 and a version of jackson-databind in your production context which is > 2.5.0

In order to be able to deserialize [{"dummyKey1":"dummyValue1"}] into a List<Entry<String, String>> variable you can:
Use Jackson's parameter names module. Read more here. It basically allows non-annotated, non default constructors with parameters to be used for deserialization of a class. In this case the constructors of the various implementations of Map.Entry. A perfectly straightforward solution if you use Java 8 anyway.
If you can't use the parameter names module (e.g. Java 7), you can look into using mixins to annotate a constructor of a class without modifying it's source code. I had a go at that and it's tricky. For HashMap for instance the implementation of Map.Entry is Node which has package private visibility.

Related

Camel Quarkus does not seem to be using the Quarkus objectmapper

I have a Camel ReST route that uses Jackson to convert a collection to JSON but it's throwing an error when the object in the collection includes a LocalDate (everything works fine without LocalDates).
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type java.time.LocalDate not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
I have added a class to customise the Quarkus ObjectMapper:
#Singleton
class MyObjectMapperCustomizer : ObjectMapperCustomizer {
override fun customize(objectMapper: ObjectMapper) {
objectMapper.registerModule(JavaTimeModule())
}
}
but it looks like Camel is not using this ObjectMapper and I can see in VisualVM that there are 3 instances of the ObjectMapper class.
The Camel rest endpoint that's throwing the exception is:
.get().produces(MediaType.APPLICATION_JSON).route()
.bean(svc.getAllTradeList()).marshal().json(JsonLibrary.Jackson).endRest()
The svc.getAllTradeList just sets the exchange body to a list of Trade objects and the Trade object itself is pretty basic:
#RegisterForReflection
data class Trade(
val id: String,
val description: String,
val notional: Double,
val tradeDate: LocalDate
)
I can't see an obvious way to make Camel use the Quarkus ObjectMapper that I'm customising or to customise the one that Camel appears to be creating.
Any pointers would be much appreciated.
If there are multiple instances of the object mapper, then camel does not know which one to pick so to use it, you have to explicit configure what camel should use:
If the object mapper from quarkus is named, you can do something like:
.marshal()
.json()
.libray(JsonLibrary.Jackson)
.objectMapper("name-of-the-object-mapper")
if not, you can create an instance of the JacksonDataFormat and configure it:
JacksonDataFormat df = new JacksonDataFormat();
df.setObjectMapper(mapper) // use CDI to get a referece
And then use it directly:
.marshal(mapper)

Checking if a KClass is an object

I have the following Kotlin code:
fun isObject(type: KClass<*>) = type.objectInstance != null
fun main() {
println(isObject(emptyMap<Int, Int>()::class))
}
which produces the following errror:
Exception in thread "main" java.lang.IllegalAccessException: class kotlin.reflect.jvm.internal.KClassImpl$Data$objectInstance$2 cannot access a member of class kotlin.collections.EmptyMap with modifiers "public static final"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
at java.base/java.lang.reflect.Field.get(Field.java:416)
at kotlin.reflect.jvm.internal.KClassImpl$Data$objectInstance$2.invoke(KClassImpl.kt:114)
at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:62)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
at kotlin.reflect.jvm.internal.KClassImpl$Data.getObjectInstance(KClassImpl.kt)
at kotlin.reflect.jvm.internal.KClassImpl.getObjectInstance(KClassImpl.kt:239)
I want my isObject function to work for any arbitrary KClass but I don't know how to do it without checking if the object instance is non null. Any suggestions?
If you don't mind some reflection overhead and that it only works with Kotlin/JVM then you can use my library fluid-meta for that:
fun isObject(type: KClass<*>) = Meta.of(type) is MObject
Use version 0.9.16 if you're still on Kotlin 1.3 and the library won't work otherwise.
It uses the hidden Kotlin metadata annotations added to each class generated by Kotlin. If these annotations get stripped in your project at some point (e.g. by an aggressive ProGuard) then you won't have that information at runtime anymore though.

spring data rest kotlin associations POST

I followed the tutorial http://www.baeldung.com/spring-data-rest-relationships.
I also observed that I can create the association directly by providing the link to the relationship.
curl -i -X POST -H "Content-Type:application/json" -d '{"name":"My Library"}' http://localhost:8080/libraries
curl -i -X POST -d '{"title":"Books", "library":"http://localhost:8080/libraries/1"}' -H "Content-Type:application/json" http://localhost:8080/books
This works fine in Java and also in Kotlin when using a regular class.
However, if I use a data class in Kotlin, I get the following error
2018-04-26 14:13:43.730 ERROR 79256 --- [nio-8080-exec-2] b.e.h.RestResponseEntityExceptionHandler : org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.baeldung.models.Library (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/libraries/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.baeldung.models.Library (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/libraries/1') at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 29] (through reference chain: com.baeldung.models.Book["library"])
I do have the relevant kotlin-spring, kotlin-jpa and kotlin-noarg plugins in my project.
Code is here https://github.com/vijaysl/spring-data-rest
Try adding #JsonCreator(mode = JsonCreator.Mode.DISABLED) annotation on primary constructor. No need to disable the com.fasterxml.jackson.module:jackson-module-kotlin.
Explanation:
Kotlin Jackson module implies your default constructor is the JSON creator (see KotlinValueInstantiator class).
Therefore, Spring Data REST does not apply its bean deserializer modifier (that is supposed to load a bean by URI) because bean properties mappings are not used for creator properties (constructor params).
KotlinValueInstantiator tries to deserialize constructor params using standard deserializers and instantiators and this leads to the error you mentioned.
Possible solution:
Since koltin-jpa module adds a default empty constructor for JPA, you can instruct Jackson not to use the JSON creator but the default empty constructor by explicitly disabling it.
Example:
#Entity
class Book #JsonCreator(mode = JsonCreator.Mode.DISABLED) constructor(
#ManyToMany
val libraries: ModifiableList<Library> = ArrayList(),
): AbstractPersistable<Long>(), Identifiable<Long>
Kotlin data classes are pretty strict. It's telling you, basically, it can't construct your POKO and it's listing some of the ways it tries. One of them is with a String constructor. Others are through private field manipulation (which is the way it's been done normally).
Data classes in kotlin, if they have fields declared as private val name:String translate to (in java) private final String name; It can't assign to a final field (which is dirty to try to assign to a private field, but impossible when it's final; the JVM won't allow it) and there are no getName() or setName() functions which can be used as another method of hydration.
Some options:
Declare your variables are var instead of val. private var name:String is java equiavalent to private String name which will use field based (dirty) hydration.
include a specific kotlin dependency for kotlin that fixes this issue: compile("com.fasterxml.jackson.module:jackson-module-kotlin") have a look at this project
example kotlin class that should work for you:
import org.springframework.hateoas.Identifiable
import java.time.LocalDate
import javax.persistence.*
import javax.validation.constraints.*
#Entity
data class Employee(#Pattern(regexp = "[A-Za-z0-9]+")
#Size(min = 6, max = 32)
val name: String,
#Email
#NotNull
val email: String?,
#PastOrPresent
val hireDate: LocalDate = LocalDate.now(),
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL])
val forms:List<Form> = listOf(),
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL])
val reports:List<Report> = listOf(),
#Id #GeneratedValue( strategy = GenerationType.IDENTITY) private val id: Long? = null): Identifiable<Long> {
override fun getId() = id
constructor(name:String): this(name,"$name#foo.com")
}
With kotlin all Ok.
Just replace "data class" to "class".
Jackson don't find empty constructor in "data class". And use other deserializator... not Uri....

kotlin : cannot parse string to enum with Jackson

I use the lib jackson-module-kotlin to parse string of json into object.
My issue is when I parse a string into an enum , and when I launch with intellij, I have this stack trace:
Caused by: kotlin.reflect.jvm.internal.KotlinReflectionInternalError:
Reflection on built-in Kotlin types is not yet fully supported. No
metadata found for public final val name: kotlin.String defined in
kotlin.Enum[DeserializedPropertyDescriptor#212b316a]
I don't have this issue when I launch with maven.
I use kotlin 1.1.51, with intellij kotlin plugin 1.2.0-release-IJ2017.3-1, I target a JVM 1.8, and i use jackson-module-kotlin version 2.8.7
what should I do?
enum class CType { DEAL, FILE }
data class Code(val code: String, val type: CType)
fun testDeserialization() {
val mapper = jacksonObjectMapper()
// following line throws an exception:
mapper.readValue("""{"code":"A","type":"DEAL"}""", Code::class.java)
}
The only way I got it working is by adding additional #JvmStatic annotation. I had mapper.registerModule(new KotlinModule()); and all, nothing worked but this:
package nc.features.algo.model
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
enum class LHStatus (
#get:JsonValue val code: Int
) {
LH_POS_OVU_WAITING(1),
LH_NEG_OVU_WAITING(2),
;
companion object {
#JsonCreator
#JvmStatic
fun deser(code: Int?): LHStatus? {
if (code == null) return null
for (i in values()) {
if (i.code == code) return i
}
return null
}
}
}
You have to do a few things.
Update Jackson dependencies to the latest version (right now, 2.9.4).
Update Kotlin version to a version equal or greater than 1.3.0.
Be sure to add the following dependencies to your build.gradle:
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"
... then you call registerKotlinModule() on your Jackson ObjectMapper and the code of your enum should be just like this:
enum class CType(#get:JsonValue val value: String) {
DEAL("deal"),
FILE("file");
companion object {
#JsonCreator
fun fromString(value: String): CType? {
for (type in CType.values()) {
if (type.name.equals(value, true)) {
return gender
}
}
return null
}
}
}
Intellij is most likely using the kotlin compiler version 1.2.0 (from the plugin) and it doesn't seem to support reflection properly.
I suggest you do one of the following:
Upgrade your kotlin version in maven and the intellij kotlin plugin to newer versions (e.g. 1.2.30). If you do that, you also have to update jackson-module-kotlin to >= 1.9, since there is an incompatibility with kotlin 1.2 (see here).
Set the kotlin compiler version to 1.1 in Intellij Idea settings.
It is generally a good idea to use the same version of kotlin in Intellij Idea and maven/gradle.
You need to use the Kotlin module for Jackson that is compatible with Kotlin 1.2.x, this includes minimally these three versions of the module:
2.9.4.1 (works with any 2.9.x of Jackson, but best to use most recent)
2.8.11.1 (for Jackson 2.8.x)
2.7.9.1 (for Jackson 2.7.x)
Otherwise, you will run into a problem with library mismatches.
The jackson-module-kotlin homepage lists these as the current versions, but they are likely to change and you can check the various Maven repository search engines to see which library versions are available and which dependencies they have on Kotlin to find matching versions.
Also note you can import the extensions for the ObjectMapper class and use reified types, so instead of:
val something = mapper.readValue("""{"code":"A","type":"DEAL"}""", Code::class.java)
you would have:
val something: Code = mapper.readValue("""{"code":"A","type":"DEAL"}""")
or alternatively:
val something = mapper.readValue<Code>("""{"code":"A","type":"DEAL"}""")
It is usually bad to use the erased type (i.e. Whatever::class.java) since this does not work for anything with generic type parameters, and using reified types also works nicely when deserializing into collections.

How do I get the class name from a type name?

I am trying to deserialize a Json string into an object of type OperationResult<String> using Jackson with Kotlin.
I need to construct a type object like so:
val mapper : ObjectMapper = ObjectMapper();
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(*/ class of OperationResult */,,
/* class of String */);
val result : OperationResult<String> = mapper.readValue(
responseString, type);
I've tried the following but they do not work.
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
javaClass<OperationResult>,
javaClass<String>); // Unresolved javaClass<T>
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
OperationResult::class,
String::class);
How do I get a java class from the type names?
You need to obtain instance of Class not KClass. To get it you simply use ::class.java instead of ::class.
val type : JavaType = mapper.typeFactory.constructParametricType(OperationResult::class.java, String::class.java)
Kotlin has a few things that become a concern when using Jackson, GSON or other libraries that instantiate Kotlin objects. One, is how do you get the Class, TypeToken, TypeReference or other specialized class that some libraries want to know about. The other is how can they construct classes that do not always have default constructors, or are immutable.
For Jackson, a module was built specifically to cover these cases. It is mentioned in #miensol's answer. He shows an example similar to:
import com.fasterxml.jackson.module.kotlin.* // added for clarity
val operationalResult: OperationalResult<Long> = mapper.readValue(""{"result":"5"}""")
This is actually calling an inline extension function added to ObjectMapper by the Kotlin module, and it uses the inferred type of the result grabbing the reified generics (available to inline functions) to do whatever is needed to tell Jackson about the data type. It creates a Jackson TypeReference behind the scenes for you and passes it along to Jackson. This is the source of the function:
inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
You can easily code the same, but the module has a larger number of these helpers to do this work for you. In addition it handles being able to call non-default constructors and static factory methods for you as well. And in Jackson 2.8.+ it also can deal more intelligently with nullability and default method parameters (allowing the values to be missing in the JSON and therefore using the default value). Without the module, you will soon find new errors.
As for your use of mapper.typeFactory.constructParametricType you should use TypeReference instead, it is much easier and follows the same pattern as above.
val myTypeRef = object: TypeReference<SomeOtherClass>() {}
This code creates an anonymous instance of a class (via an object expression) that has a super type of TypeRefrence with your generic class specified. Java reflection can then query this information.
Be careful using Class directly because it erases generic type information, so using SomeOtherClass::class or SomeOtherClass::class.java all lose the generics and should be avoided for things that require knowledge of them.
So even if you can get away with some things without using the Jackson-Kotlin module, you'll soon run into a lot of pain later. Instead of having to mangle your Kotlin this module removes these types of errors and lets you do things more in the "Kotlin way."
The following works as expected:
val type = mapper.typeFactory.constructParametricType(OperationalResult::class.java, String::class.java)
val operationalResult = mapper.readValue<OperationalResult<String>>("""{"result":"stack"}""", type)
println(operationalResult.result) // -> stack
A simpler alternative to deserialize generic types using com.fasterxml.jackson.core.type.TypeReference:
val operationalResult = mapper.readValue<OperationalResult<Double>>("""{"result":"5.5"}""",
object : TypeReference<OperationalResult<Double>>() {})
println(operationalResult.result) // -> 5.5
And with the aid of jackson-kotlin-module you can even write:
val operationalResult = mapper.readValue<OperationalResult<Long>>("""{"result":"5"}""")
println(operationalResult.result)