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
Related
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.
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.
I'm building a schema generator and I'm trying to get the JsonProperty of enum values, for example, for this class:
enum class Size {
#JsonProperty("really-tall") TALL,
#JsonProperty("really-grande") GRANDE;
}
I'd like to get the list "really-tall", "really-grande".
How do I access the annotation of an enum?
Thanks!
UPDATE:
Solution based on this reply for a generic KType:
return (jvmErasure.java as Class<Enum<*>>)
.enumConstants
.map {
it.javaClass.getField(it.name).getAnnotation(JsonProperty::class.java)?.value // Get the JsonProperty string first if exists
?: it.name
}
Update: Additional question from OP
How do I make the first approach work for a generic KType
inline fun <reified T : Enum<T>> getJsonPropertyAnnotations() = enumValues<T>().map {
it.declaringClass
.getField(it.name)
.getAnnotation(JsonProperty::class.java)
.value
}
class SomeTest : StringSpec({
"getJsonPropertyAnnotations" {
getJsonPropertyAnnotations<Size>()
shouldBe listOf("really-tall", "really-grande")
}
})
Please note that with Kotlin 1.7, IntelliJ may show a deprecation warning with wrong replacement for declaringClass in getJsonPropertyAnnotations. I guess this will be sorted out in later versions. Link to related source
The following code should do what you want.
class SomeTest : StringSpec({
"getting annotation values" {
val result = enumValues<Size>().map {
it.declaringClass.getField(it.name).getAnnotation(JsonProperty::class.java).value
}
result shouldBe listOf("really-tall", "really-grande")
}
})
An alternative (less code): Add a String property to your enum class (I called it someFieldName in the below code), annotate with #get:JsonValue, and construct each enum entry with the string value you want. #get:JsonValue will use someFieldName instead of the enum value during serialization.
enum class Size(#get:JsonValue val someFieldName: String) {
TALL("really-tall"),
GRANDE("really-grande");
}
Same test again
class SomeTest : StringSpec({
"getting prop values" {
val result = enumValues<Size>().map {
it.someFieldName
}
result shouldBe listOf("really-tall", "really-grande")
}
})
We're using the latter approach in an ongoing project.
Here's a Kotlin implementation of the technique suggested by user #aSemy.
It's an extension function on ObjectMapper for asking the mapper how it would serialize the values of an enum. This is more robust that just inspecting the #JsonProperty annotation, since it works with #JsonValue as well as any custom annotation introspectors registered with the mapper.
inline fun <reified T : Enum<T>> ObjectMapper.enumValues() : List<String> {
return convertValue(
kotlin.enumValues<T>(),
jacksonTypeRef<List<String>>()
)
}
Usage:
println(jsonMapper().enumValues<Size>())
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.
I have a gson model class which contains a variable named duration which is an integer. The problem is that when this variable has no value the server instead of returning me null it returns "".
This is also the case with lists, when I am expecting a List<String>? and there is no value in them I get "" instead of null. Unfortunately the server can not change.
I have fixed the problem with a custom double deserializer but I need something more generic because the "" is the default server approach. Here is my progress so far but on the else branch I do not know how to tell it to continue with the default deserializer.
internal class BadDeserializer : JsonDeserializer<Any> {
#Throws(JsonParseException::class)
override fun deserialize(
json: JsonElement,
type: Type,
context: JsonDeserializationContext): Any? {
return try {
val json = json.asString
if (json.isBlank()) {
null
} else {
//Do nothing and call default behavior (?)
}
} catch (e: Exception) {
throw JsonParseException(e)
}
}
}
That's an unfortunate response getting an empty string.
AFAIK you can't register a type adapter for primitive types with GSON, though perhaps its possible to register one for their boxed types (not sure how this may work with Kotlin).
If you can use the boxed types, then you could use something like the following:
public class DoubleAdapter implements JsonDeserializer<Double> {
#Override
public Double deserialize(final JsonElement json,
final Type typeOfT,
final JsonDeserializationContext ctx) throws JsonParseException {
if (!json.isJsonPrimitive()) {
throw new JsonParseException("primitive expected");
}
final JsonPrimitive primitive = json.getAsJsonPrimitive();
if (primitive.isString()) {
final String value = primitive.getAsString();
if ("".equals(value)) return null;
throw new JsonParseException("double expected, not string");
}
if (!primitive.isNumber()) {
throw new JsonParseException("double expected");
}
return primitive.getAsNumber().doubleValue();
}
}
Though it might be cleaner (and I usually do for everything) to write an adapter for whatever structure you have.