Unmarshal JSON to String, BigInteger and BigDecimal with jackson very close - jackson

We are using jackson, and I see this in the code
DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS
DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS
But how do I get jackson to use those features now?
This would be the perfect situation. I just want a Map result with String, BigDecimal and BigIntegers.

Enable the feature on the ObjectMapper.
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationConfig.Feature.…);
Update for version >= 2.0.0:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
mapper.enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);

Related

Jackson parsing fails for json string with encoded value

I have a encrypted string, if I try to use ObjectMapper readTree, then I get Json parse exception.. Any idea how I can let json parser know this to consider the value as string..
{"a" :"encryptedStr"}
Sample code:
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(messageBody);

Why #JsonProperty not work for camel case properties in Kotlin

There is a very simple class:
class Price(
#JsonProperty("YPRICE")
val yprice: String? = null,
#JsonProperty("ZPRICE")
val zPrice: String? = null
)
And the following code to serialize to string:
val mapper = ObjectMapper().registerKotlinModule()
mapper.writeValue(System.out, Price())
The result in console is:
{"YPRICE":null,"zprice":null}
If changing the property of zPrice to zprice, then the result changes to:
{"YPRICE":null,"ZPRICE":null}
And if changing the property of yprice to yPrice, then the result changes to:
{"yprice":null,"zprice":null}
It seems that #JsonProperty does not work for the camel case properties.
You need to instruct ObjectMapper to generate JSON properties based on fields and not based on getter methods. You can use com.fasterxml.jackson.annotation.JsonAutoDetect annotation:
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.ANY)
class Price(
Since now, in all cases you should see the same result.
Take a look at:
Jackson/Hibernate, meta get methods and serialization
How to ignore "Is' methods with Jackson 2.2.3
InvalidDefinitionException: No serializer found for inner class

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)

Ktor with Kmongo and kotlinx.serialization

I have a setup where I use KTor with KMongo and Kotlinx.Serialization.
The Kmongo part works, I can get and put my Class
#Serializable
data class Task(#ContextualSerialization #SerialName("_id") val _id : Id<Task> = newId(),
val title : String = "",
val description : String = ""
)
Into the database and retrieve it. That all works flawlessly.
But when I try to send that object through a rest call to the frontend, again with Kotlinx.Serialization.
get<Tasks>{ task ->
val dao by di().instance<Dao>();
val task = Task( title = "task1", description = "task1description");
val foundTask = dao.read(task);
if(foundTask != null){
call.respond(foundTask)
} else {
call.respond("didn't find anything")
}
}
It throws this expection:
kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class WrappedObjectId. For generic classes, such as lists, please provide serializer explicitly.
at kotlinx.serialization.PlatformUtilsKt.serializer(PlatformUtils.kt:21)
at kotlinx.serialization.modules.SerialModuleExtensionsKt.getContextualOrDefault(SerialModuleExtensions.kt:29)
at kotlinx.serialization.ContextSerializer.serialize(ContextSerializer.kt:29)
at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:227)
at kotlinx.serialization.builtins.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:72)
Now I figured out that this is because there are 2 instances of the kotlin.serialization json. and the one on KMongo does not share it's serializers with the other one.
so I added the serializers from KMongo to the other instance from Ktor
install(ContentNegotiation) {
json(
json = Json(DefaultJsonConfiguration.copy(prettyPrint = true), context = kmongoSerializationModule),
contentType = ContentType.Application.Json
)
}
and now I get
java.lang.ClassCastException: class kotlinx.serialization.json.internal.StreamingJsonOutput cannot be cast to class com.github.jershell.kbson.BsonEncoder (kotlinx.serialization.json.internal.StreamingJsonOutput and com.github.jershell.kbson.BsonEncoder are in unnamed module of loader 'app')
So my question is why is it happening and how to fix it?
Edit: This issue has been fixed.
I also posted this question on the KMongo github and basically got an instant response that this was fixed for Jackson but not yet for Kotlinx.serialization.
Someone is going to fix this.
Edit: It has been fixed

Issue when serializing Map.Entry using jackson

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.