unmapped target property and mapstruct calls constructor with null parameter - properties

I'm exploring mapstruct to map JPA entities and DTO objects. Entities and DTOs have abstract base classes that contain id and version fields that I'd like to keep private so that they can not be modified (public getter, no setter for both types). I made a most simple reproducer to demonstrate the idea. Abstract Base class has a private field name. To copy the field values back and forth Base defines a constructor that has a Base parameter. The constructor picks the private field from the parameter and assigns it to it's own private field:
package de.ruu.lab.map.read_only_field_in_base;
public abstract class Base
{
private String name;
public Base(String name) { this.name = name; }
protected Base(Base source) { name = source.name; }
public String getName() { return name; }
}
These are the subclasses of Base:
package de.ruu.lab.map.read_only_field_in_base;
import de.ruu.lab.map.read_only_field_in_base.SimpleMapper.Default;
public class Source extends Base
{
public Source(String name) { super(name); }
#Default
public Source(Base base) { super(base); }
}
package de.ruu.lab.map.read_only_field_in_base;
import de.ruu.lab.map.read_only_field_in_base.SimpleMapper.Default;
public class Target extends Base
{
public Target(String name) { super(name); }
#Default
public Target(Base base) { super(base); }
}
I have to annotate the default constructor to resolve constructor ambiguity for mapstruct. The mapper looks like this:
package de.ruu.lab.map.read_only_field_in_base;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.RetentionPolicy.CLASS;
import java.lang.annotation.Retention;
import org.mapstruct.Mapper;
import org.mapstruct.Qualifier;
import org.mapstruct.factory.Mappers;
#Mapper
public interface SimpleMapper
{
SimpleMapper INSTANCE = Mappers.getMapper(SimpleMapper.class);
Source toSource(Target target);
Target toTarget(Source source);
#Qualifier // make sure that this is the MapStruct qualifier annotation
#java.lang.annotation.Target(CONSTRUCTOR)
#Retention(CLASS)
public #interface Default { }
}
The first problem is that mapstruct warns that there is an unmapped target property "base". What does that mean? Which target property is not mapped? Wouldn't it be possible to print the name of the property in the warning? I use eclipse as IDE, maybe the behaviour is different with other tools?
I tried annotating the mapping methods with
#Mapping(target="name", ignore = true)
but that does not let the warning disappear.
Because mapstruct just makes a warning I hoped everything would be ok and I created a tiny test class:
package de.ruu.lab.map.read_only_field_in_base;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
class SimpleMapperTest
{
#Test void shouldMapSourceToTarget()
{
Source source = new Source("map me");
Target target = SimpleMapper.INSTANCE.toTarget(source);
assertThat(target.getName(), is(source.getName()));
}
#Test void shouldMapTargetToSource()
{
Target target = new Target("map me");
Source source = SimpleMapper.INSTANCE.toSource(target);
assertThat(source.getName(), is(target.getName()));
}
}
Both tests fail with a NPE because of some strange code mapstruct generated:
package de.ruu.lab.map.read_only_field_in_base;
import javax.annotation.processing.Generated;
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-09-18T11:08:20+0200",
comments = "version: 1.5.2.Final, compiler: Eclipse JDT (IDE) 1.4.200.v20220802-0458, environment: Java 17.0.2 (GraalVM Community)"
)
public class SimpleMapperImpl implements SimpleMapper {
#Override
public Source toSource(Target target) {
if ( target == null ) {
return null;
}
Base base = null;
Source source = new Source( base );
return source;
}
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Base base = null;
Target target = new Target( base );
return target;
}
}
Obviously code like this causes the NPE:
Base base = null;
Source source = new Source( base );
IMO this would be correct ("target" is the name of the method's parameter):
Source source = new Source( target );
Maybe this can be solved in an upcoming version. Meanwhile, is there any recommendation how to deal with this now?
Thanks!

The reason why you are getting the warnings is the fact that MapStruct doesn't really care about the private / protected fields you have.
When performing a mapping MapStruct looks at the setters and the constructor parameters to decide which properties need to be mapped.
Looking at your examples you have annotated the constructor that takes Base as a default constructor. This means that from the point of view of MapStruct your objects have properties that are taking Base and thus there is the warning for the unmapped property base.
There are 2 ways that you can do to fix this:
Instruct MapStruct how to map to the base property
#Mapper
public interface SimpleMapper
{
SimpleMapper INSTANCE = Mappers.getMapper(SimpleMapper.class);
#Mapping(target = "base", source = "target")
Source toSource(Target target);
#Mapping(target = "base", source = "source")
Target toTarget(Source source);
}
using the #Mapping you will tell MapStruct that you want to map the source parameters to the base property.
Annotated the constructor with the string a #Default
Instead of annotating the constructor that takes Base as input you can annotate the constructor that takes String as a default constructor.
This way MapStruct will look for how to map a property with the name name and thus will use the public Base#getName method to perform the mapping.
Note: I saw that you have #Qualifier on the #Default annotation, this is not needed. The only requirement for MapStruct for the default annotation is that it needs to be named like that.

Related

Kotlin NoArg plugin ignores declaration assignment

This problem is related to Kotlin noarg plugin not initializing default values.
I've got my NoArg plugin set up like this:
plugins {
id "org.jetbrains.kotlin.plugin.noarg" version "1.7.10"
}
noArg {
annotation("tools.AddEmptyConstructor")
}
Now, this kotlin code:
import tools.AddEmptyConstructor
#AddEmptyConstructor
class Test {
private constructor(test: String)
private val declarationAssignment = ArrayList<String>()
}
compiles to this java code:
import tools.AddEmptyConstructor;
public final class Test {
private final ArrayList declarationAssignment;
private Test(String test) {
this.declarationAssignment = new ArrayList();
}
public Test() {
}
}
I get that the empty constructor is supposed to be empty, but why is the declaration assignment, which isn't part of the constructor during compile time, not also being copied into the empty constructor?
How is that intended to be done? I don't have to use declaration assignments and I would be happy with workarounds, but I cannot access the empty constructor and thus not initialize values at all.

Accessing a Kotlin extension function from Java

Is it possible to access extension functions from Java code?
I defined the extension function in a Kotlin file.
package com.test.extensions
import com.test.model.MyModel
/**
*
*/
public fun MyModel.bar(): Int {
return this.name.length()
}
Where MyModel is a (generated) java class. Now, I wanted to access it in my normal java code:
MyModel model = new MyModel();
model.bar();
However, that doesn't work. The IDE won't recognize the bar() method and compilation fails.
What does work is using with a static function from kotlin:
public fun bar(): Int {
return 2*2
}
by using import com.test.extensions.ExtensionsPackage so my IDE seems to be configured correctly.
I searched through the whole Java-interop file from the kotlin docs and also googled a lot, but I couldn't find it.
What am I doing wrong? Is this even possible?
Perhaps like this:
// CallExtensionFunction.java
package com.example.groundup;
public class CallExtensionFunction {
public static void main(String[] args) {
MyModel myModel = new MyModel();
int bar = MyModelKt.bar(myModel);
System.out.println(bar);
}
}
// MyModell.kt
package com.example.groundup
fun MyModel.bar(): Int {
return this.name.length
}
class MyModel() {
val name = "Hugo"
}
The extension function is provided in the corresponding singleton with the suffix "Kt" as a static method.

Gson - deserialize or default

I have a class :
data class Stam(#SerializedName("blabla") val blabla: String = "")
I want to do gson.fromJson("{\"blabla\":null}", Stam::class.java)
However, it will fail because blabla is not nullable.
I want to make it so if gson failed to deserialize some variable, it will take the default value I give it.
How to achieve that?
I don't think it is possible with GSON, this is one of the reasons why kotlinx.serialization library was created. With this library it is fairly easy:
#Serializable
data class Stam(#SerialName("blabla") val blabla: String = "") //actually, #SerialName may be omitted if it is equal to field name
Json { coerceInputValues = true }.decodeFromString<Stam>("{\"blabla\":null}")
I wouldn't say it is not possible in Gson, but Gson is definitely not the best choice:
Gson has no mention on Kotlin, its runtime and specifics, so one is better to use a more convenient and Kotlin-aware tool. Typical questions here are: how to detect a data class (if it really matters, can be easily done in Kotlin), how to detect non-null parameters and fields in runtime, etc.
Data classes in Kotlin seem to provide a default constructor resolvable by Gson therefore Gson can invoke it (despite it can instantiate classes instances without constructors using unsafe mechanics) delegating to the "full-featured" constructor with the default arguments. The trick here is removing null-valued properties from input JSON so Gson would keep "default-argumented" fields unaffected.
I do Java but I do believe the following code can be converted easily (if you believe Gson is still a right choice):
final class StripNullTypeAdapterFactory
implements TypeAdapterFactory {
// The rule to check whether this type adapter should be applied.
// Externalizing the rule makes it much more flexible.
private final Predicate<? super TypeToken<?>> isClassSupported;
private StripNullTypeAdapterFactory(final Predicate<? super TypeToken<?>> isClassSupported) {
this.isClassSupported = isClassSupported;
}
static TypeAdapterFactory create(final Predicate<? super TypeToken<?>> isClassSupported) {
return new StripNullTypeAdapterFactory(isClassSupported);
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !isClassSupported.test(typeToken) ) {
return null;
}
// If the type is supported by the rule, get the type "real" delegate
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, typeToken);
return new StripNullTypeAdapter<>(delegate);
}
private static final class StripNullTypeAdapter<T>
extends TypeAdapter<T> {
private final TypeAdapter<T> delegate;
private StripNullTypeAdapter(final TypeAdapter<T> delegate) {
this.delegate = delegate;
}
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegate.write(out, value);
}
#Override
public T read(final JsonReader in) {
// Another disadvantage in using Gson:
// the null-stripped object must be buffered into memory regardless how big it is.
// So it may generate really big memory footprints.
final JsonObject buffer = JsonParser.parseReader(in).getAsJsonObject();
// Strip null properties from the object
for ( final Iterator<Map.Entry<String, JsonElement>> i = buffer.entrySet().iterator(); i.hasNext(); ) {
final Map.Entry<String, JsonElement> property = i.next();
if ( property.getValue().isJsonNull() ) {
i.remove();
}
}
// Now there is no null values so Gson would only use properties appearing in the buffer
return delegate.fromJsonTree(buffer);
}
}
}
Test:
public final class StripNullTypeAdapterFactoryTest {
private static final Collection<Class<?>> supportedClasses = ImmutableSet.of(Stam.class);
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
// I don't know how easy detecting data classes and non-null parameters is
// but since the rule is externalized, let's just lookup it
// in the "known classes" registry
.registerTypeAdapterFactory(StripNullTypeAdapterFactory.create(typeToken -> supportedClasses.contains(typeToken.getRawType())))
.create();
#Test
public void test() {
final Stam stam = gson.fromJson("{\"blabla\":null}", Stam.class);
// The test is "green" since
Assertions.assertEquals("", stam.getBlabla());
}
}
I still think Gson is not the best choice here.

Kotlin data class with different backing field type

I have a simple class used for JSON serialization. For this purpose, the external interface uses Strings, but the internal representation is different.
public class TheClass {
private final ComplexInfo info;
public TheClass(String info) {
this.info = new ComplexInfo(info);
}
public String getInfo() {
return this.info.getAsString();
}
// ...more stuff which uses the ComplexInfo...
}
I have this working in Kotlin (not sure if there's a better way). But the non-val/var constructor prevents me from using data.
/*data*/ class TheClass(info: String) {
private val _info = ComplexInfo(info)
val info: String
get() = _info.getAsString()
// ...more stuff which uses the ComplexInfo...
}
How do I get this working as a data class?
You can use a combination of a private ComplexInfo property declared in the primary constructor and a secondary constructor that accepts a String.
Optionally, make the primary constructor private.
Example:
data class TheClass private constructor(private val complexInfo: ComplexInfo) {
constructor(infoString: String) : this(ComplexInfo(infoString))
val info: String get() = complexInfo.getAsString()
}
Note that it's the complexInfo property that is used in the data class generated members implementations.

ignoring unknown fields on JSON objects using jackson

I am using jackson 2.x for serialization and deserialization. I am registered the objectMapper to the afterBurner module and configured the object mapper to ignore unknown properties
objectMapper.registerModule(new AfterBurnerModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
but when it is trying to serialize an object it is failing with unknown field found for attribute error
The java object is also annotated with #JsonIgnoreProperties(ignoreUnknown = true)
Can some one help me understand what might be going wrong
Below is the Util class
package example;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
public final class Util {
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new AfterburnerModule());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
objectMapper.setDateFormat(sdf);
objectMapper.setAnnotationIntrospector(AnnotationIntrospector.pair(new JaxbAnnotationIntrospector(objectMapper.getTypeFactory()), new JacksonAnnotationIntrospector()));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
}
private Util() {
}
public static <T> T convertToObject(String jsonString,Class<T> classType){
T obj = null;
try {
obj = objectMapper.readValue(jsonString, classType);
} catch (Exception e) {
}
return obj;
}
public static String convertToString(Object obj)
throws IOException {
return objectMapper.writeValueAsString(obj);
}
}
enum class NumberEnum
package sample;
public enum NumberEnum {
ONE, TWO
}
class A
package sample;
#JsonIgnoreProperties(ignoreUnknown = true)
public class A {
#JsonProperty
private NumberEnum number;
}
the code where i am deserializing is as below
A a = Util.convertToObject(str, A.class);
and the string i am trying to deserailize is as below:
{
"number": "Num"
}
Below is the error while deserializing:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of sample.NumberEnum from String value 'Num': value not one of declared Enum instance names: [ONE, TWO]
at (through reference chain: sample.A["a"]->sample.NumberEnum["number"])
the class A is imported from a jar and it is using jackson library 1.9
ignoreUnknown only applies to property names that are unknown in the destination object. For example, if you had:
{
"number": "ONE",
"foo": "bar"
}
Jackson would normally fail if the object you're trying to deserialize had no setter/property named "foo".
What you're trying to accomplish is entirely different; the property is known, but you're trying to handle an invalid enum value. If you just want it to deserialize unknown values as null, use READ_UNKNOWN_ENUM_VALUES_AS_NULL:
Feature that allows unknown Enum values to be parsed as null values. If disabled, unknown Enum values will throw exceptions. (...) Feature is disabled by default.
This is done via mapper configuration:
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
Note: I just saw that you're using Jackson 1.9, and this deserialization feature was released in 2.0. If upgrading is not an option, you might need to create a custom deserializer for this property which does the same thing.
I think what you need to deserialize is actually json that looks like this:
{
"number": "ONE"
}
- OR -
{
"number": "TWO"
}
since "Num" is not a the name() of either of your enums it will not deserialize