Jackson XML works with class but not with record - jackson

I have a very simple XML - just one tag:
<Multiple/>
And I'm trying to parse it using Jackson:
#Test
void testParseEmptyMultiple() throws Exception {
var xml = """
<Multiple/>""";
var root = new Multiple(null);
var result = xmlMapper.readValue(xml, Multiple.class);
assertEquals(root, result);
}
Here's the mapping record:
package myawesomeapp.mappings;
import java.util.Collection;
public record Multiple(Collection<Single> single) {
}
It seems all good, but when I run the test, I get this error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `myawesomeapp.mappings.Multiple` (although at least one Creator exists): no default no-arguments constructor found
Adding a default no-arguments constructor makes no difference, but changing from record to class (adding all the necessary stuff) doew. What am I missing here?
Note: If I add Single tags inside that one, it also works.

Related

Subtypes not being recognized in Subclasses

I have the following code setup;
abstract class GenericQuestionEditor() {
protected abstract var data: GenericQuestionData
}
but then when I create EditorSimple() it throws an error when I try to set data to DataSimple(), why?
class EditorSimple(): GenericQuestionEditor() {
override var data = DataSimple()
}
my GenericQeustionData and DataSimple() are setup like this;
abstract class GenericQuestionData {}
class DataSimple: GenericQuestionData() {}
it doesn't complain if I create this function in GenericQuestionEditor()
fun test() {
data = DataSimple()
}
Why do I get an error on data in EditorSimple()? It should recognize it as a subtype and it should be allowed as I understand.
I feel like the answer is found in the kotlin documentation but i'm not sure how to configure it in this case since they are not passed values or part of a collection.
You need to specify the type explicitly:
class EditorSimple(): GenericQuestionEditor() {
override var data: GenericQuestionData = DataSimple()
}
Without the type annotation, the type of data would be inferred to be DataSimple, which doesn't match the type of its super class' data. Even though the types are related, you can't override writable a property with a subtype. Imagine if I did:
class SomeOtherData: GenericQuestionData()
val editor: GenericQuestionEditor = EditorSimple()
editor.data = SomeOtherData() // data is of type GenericQuestionData, so I should be able to do this
But, editor actually has a EditorSimple, which can only store DataSimple objects in data!

Using a Jackson attribute to accumulate state as a byproduct of serialization

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.

How best to return a single value of different types from function

I have a function that returns either an error message (String) or a Firestore DocumentReference. I was planning to use a class containing both and testing if the error message is non-null to detect an error and if not then the reference is valid. I thought that was far too verbose however, and then thought it may be neater to return a var. Returning a var is not allowed however. Therefore I return a dynamic and test if result is String to detect an error.
IE.
dynamic varResult = insertDoc(_sCollection,
dataRec.toJson());
if (varResult is String) {
Then after checking for compliance, I read the following from one of the gurus:
"It is bad style to explicitly mark a function as returning Dynamic (or var, or Any or whatever you choose to call it). It is very rare that you need to be aware of it (only when instantiating a generic with multiple type arguments where some are known and some are not)."
I'm quite happy using dynamic for the return value if that is appropriate, but generally I try to comply with best practice. I am also very aware of bloated software and I go to extremes to avoid it. That is why I didn't want to use a Class for the return value.
What is the best way to handle the above situation where the return type could be a String or alternatively some other object, in this case a Firestore DocumentReference (emphasis on very compact code)?
One option would be to create an abstract state class. Something like this:
abstract class DocumentInsertionState {
const DocumentInsertionState();
}
class DocumentInsertionError extends DocumentInsertionState {
final String message;
const DocumentInsertionError(this.message);
}
class DocumentInsertionSuccess<T> extends DocumentInsertionState {
final T object;
const DocumentInsertionSuccess(this.object);
}
class Test {
void doSomething() {
final state = insertDoc();
if (state is DocumentInsertionError) {
}
}
DocumentInsertionState insertDoc() {
try {
return DocumentInsertionSuccess("It worked");
} catch (e) {
return DocumentInsertionError(e.toString());
}
}
}
Full example here: https://github.com/ReactiveX/rxdart/tree/master/example/flutter/github_search

Type inference for parameterized types in case of cyclic Builder scenario

Assuming there exists the following java class:
public class Test {
static class Builder<B extends Builder<B>>{
B asBuilder() {
return (B) this;
}
}
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
}
Trying to call Test.newBuilder() in a consuming Kotlin code gives the error Type expected.
Test.newBuilder<>() has the same issue. Test.newBuilder<Test.Builder>() gives the error: One type argument expected for class Builder<B : Test.Builder<B!>!>. Since the type argument is a recursive call this can't be solved in the above fashion.
I believe this is a rather weird behavior even from Java perspective. It's strange that the Test class code was even allowed in its current form. Unfortunately, the above was a simplified version of another class that I have no control of. In reality I am trying to do
org.apache.logging.log4j.core.layout.GelfLayout.newBuilder()

Using asType with Mixin annotation

I'd like write a custom type conversion Category in Groovy. The goal is to assign the values of a Map to the fields of a Groovy bean. In the future there will be different response types. The values of of the Map are always of type String but will have to be converted into a different data type. To make this work I created a Category class that implements a method named asType. This is a simplified example of my code:
class MapCategory {
static Object asType(Map self, Class clazz) {
if(clazz == Response) {
Response response = new Response()
self.each { key, value ->
response.setProperty(key, value)
}
return response
}
DefaultGroovyMethods.asType(self, clazz)
}
}
class Response {
String result
String message
}
This works just fine when when I apply the category using the use keyword.
use(MapCategory) {
println [result: 'OK', message: 'Success'] as Response
}
However, when I try to use the #Mixin annotation instead it doesn't seem to work correctly. I get the correct response type but all fields are null.
#Mixin(MapCategory)
class MyClass {
def printResponse() {
println [result: 'OK', message: 'Success'] as Response
}
}
Does anybody know why it doesn't work correctly using the annotation?
Mixins don't work that way. You are trying to mix in the method for Map into your MyClass object. The mixin would only work if MyClass extended Map.
Instead, you want to use the use keyword like normal, and just use your category as a category.
Alternatively, you might not need it at all. Did you know that you can, by default, convert any map into any GroovyBean without extra code? Just use the map-based constructor, like so:
#groovy.transform.Canonical // Groovy 1.8, just added for automatic toString method
class Response {
String result
String message
}
println new Response([result: 'OK', message: 'Success'])
println([result: 'bad', message: 'blah'] as Response)
Notice, automatic Map conversion works both ways. It's a built-in feature of Groovy.
Of course, if you need something more complex than just assigning bean properties, this won't help.
Note: I'd give you a link, but the Groovy website appears to be broken, and I can't find code examples. :-(
EDIT: Another Suggestion
Instead of using a Category at all, why don't you let the bean itself handle it:
#groovy.transform.Canonical
class Response {
String result
String message
int num
public void setNum(String num) {
this.num = Integer.parseInt(num)
}
}
def map = [result: 'OK', message: 'Success', num: '35' ]
println map as Response