Use Swagger/OpenAPI discriminator so that Jackson serializes object correctly - jackson

We're having trouble using the OpenAPI 2.0 discriminator in way that makes both the Swagger tools and the Jackson serializer happy.
Problem: during serialization Jackson currently generates two JSON properties for the discriminator, one of them having a null value.
OpenAPI 2.0 definition
swagger: '2.0'
info:
version: v1
title: Error API
paths:
/errors:
get:
description: Stack Overflow test
responses:
'200':
description: OK
schema:
$ref: '#/definitions/SpecificError'
definitions:
GeneralError:
description: Error Base Structure
type: object
discriminator: errorType
properties:
errorType:
type: string
message:
type: string
required:
- errorType
SpecificError:
description: Test
allOf:
- $ref: "#/definitions/GeneralError"
AFAIU the discriminator is correctly defined. The spec requires it to be listed both in the properties and the required list.
The property name used MUST be defined at this schema and it MUST be in the required property list. When used, the value MUST be the name of this schema or any schema that inherits it.
Swagger codegen
What the Swagger Java codegen produces is this:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "errorType",
visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(value = SpecificError.class, name = "SpecificError"),
})
public class GeneralError {
#JsonProperty("errorType")
private String errorType = null;
// accessors, even for errorType!, follow here
The accessors for errorType come as a big surprise. As the field is only needed during serialization & deserialization regular client code shouldn't have access to it. One could even argue that the field shouldn't be there at all.
Jackson serializer
As a simple test bed I use this
SpecificError specificError = (SpecificError) new SpecificError().message("message")
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer();
writer.writeValue(System.out, specificError);
This produces {"errorType":"SpecificError","message":"message","errorType":null}.
-> errorType appears twice
Q: whose fault is it? Is my Swagger definition wrong? Should the Swagger Java codegen not generate private String errorType? Or should Jackson be able to deal with this i.e. recognize that its #JsonTypeInfo and the property of that name are actually the same thing?

Related

Adding a property using Jackson MixIn's?

I know we can use Jackson MixIn's to rename a property or to ignore a property (see examples here). But is it possible to add a property?
The added property can be:
A constant (such as a version number)
A computed value (e.g. if the source class has properties for getWidth() and getHeight(), but we want to ignore both and export a getArea() instead)
Flattened information from nested members (e.g. a class has a member Information which in turn has a member Description, and we want to have a new property for description and skipping the nesting structure of Information)
From documentation:
"Mix-in" annotations are a way to associate annotations with classes,
without modifying (target) classes themselves, originally intended to
help support 3rd party datatypes where user can not modify sources to
add annotations.
With mix-ins you can:
1. Define that annotations of a '''mix-in class''' (or interface)
2. will be used with a '''target class''' (or interface) such that it
appears
3. as if the ''target class'' had all annotations that the ''mix-in''
class has (for purposes of configuring serialization /
deserialization)
To solve your problems you can:
Create new POJO which has all required fields.
Implement custom serialiser.
Before serialisation convert POJO to Map and add/remove nodes.
Use com.fasterxml.jackson.databind.ser.BeanSerializerModifier to extend custom serialisers. See: Jackson custom serialization and deserialization.
For example, to add a constant version to each object you can wrap it in Verisoned class:
class Versioned {
private final String version;
#JsonUnwrapped
private final Object pojo;
public Versioned(String version, Object pojo) {
this.version = version;
this.pojo = pojo;
}
public String getVersion() {
return version;
}
public Object getPojo() {
return pojo;
}
}
Now, if you wrap an Arae(width, height) object:
Area area = new Area(11, 12);
String json = mapper.writeValueAsString(new Versioned("1.1", area));
output will be:
{
"version" : "1.1",
"width" : 11,
"height" : 12
}

Kotlin classes not identified as Flink valid POJO's

I am writing a Flink application in Kotlin and data classes (as well as other Kotlin classes) are not identified as valid POJO types.
The Flink documentation states that a data type is recognized as a POJO type (and allows "by-name" field referencing) if the following conditions are fulfilled:
The class is public and standalone
The class has a public no-argument constructor
All non-static, non-transient fields in the class are either public (and non-final) or have public getter and setter methods that follow Java beans naming conventions.
I receive the following when implementing a Kotlin data class, which should meet the aforementioned conditions to be recognized as a POJO:
[main] INFO org.apache.flink.api.java.typeutils.TypeExtractor -
Class class <Class> cannot be used as a POJO type because not all
fields are valid POJO fields, and must be processed as GenericType.
Please read the Flink documentation on "Data Types & Serialization"
for details of the effect on performance.
Investigating further, I reviewed Flink's TypeExtractor.isValidPojoField method # https://github.com/apache/flink/blob/master/flink-core/src/main/java/org/apache/flink/api/java/typeutils/TypeExtractor.java
In a separate project, I applied the field checks with java.lang.reflect.Modifier on a simple Kotlin data class in attempt to narrow down the issue.
data class SomeDataClass(
val topic: String = "",
val message: String = ""
)
While Kotlin class fields have public visibility by default, Modifier.isPublic recognizes the fields as private. Additionally, Modifier.isFinal recognizes the fields as final.
val clazz = SomeDataClass::class.java
val fields = clazz.declaredFields
fields.forEach { it ->
println("field: $it")
println(it.genericType)
println("public? " + Modifier.isPublic(it.modifiers))
println("final? " + Modifier.isFinal(it.modifiers))
println("transient? " + Modifier.isTransient(it.modifiers))
println("static? " + Modifier.isStatic(it.modifiers))
}
>
field: private final java.lang.String SomeDataClass.topic
class java.lang.String
public? false
final? true
transient? false
static? false
However, public getter and setter methods are created for these fields, so this object should still meet the POJO criteria.
println(clazz.declaredMethods.toList())
>
[public boolean SomeDataClass.equals(java.lang.Object),
public java.lang.String SomeDataClass.toString(),
public int SomeDataClass.hashCode(),
**public final java.lang.String SomeDataClass.getMessage(),**
public final SomeDataClass SomeDataClass.copy(java.lang.String,java.lang.String),
**public final java.lang.String SomeDataClass.getTopic(),**
public final java.lang.String SomeDataClass.component1(),
public final java.lang.String SomeDataClass.component2(),
public static SomeDataClass SomeDataClass.copy$default(SomeDataClass,java.lang.String,java.lang.String,int,java.lang.Object)]
The getter and setter methods, however, are final, which leads me to believe this is the issue.
I am relatively new to JVM development, so any help would be greatly appreciated. I have reviewed the Flink Jira, Stack Overflow, and Flink mailing list and have not found a similar issue reported.
I see at least two POJO rules violation with provided data class.
1) The class has a public no-argument constructor
By default, Kotlin will not generate overloads to functions with default parameter values (https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#overloads-generation)
So your compiled class will have only one constructor with two-parameter constructor, and no-argument constructor will not be created. To force Kotlin compiler to generate multiple overloads one should use #JvmOverloads annotation. In your case it will be used on constructor so we also need to add constructor keyword:
data class SomeDataClass #JvmOverloads constructor
2) All non-static, non-transient fields in the class are either public (and non-final) or have public getter and setter methods that follow Java beans naming conventions.
Since you are using val keywords the generated fields will be final, and no setter will be generated for them. So you can change vals to vars and the fields will no longer be final and proper getters and setters will be generated too. (Or you could use another annotation to prevent generating getters and setters and expose a field as it is https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#instance-fields)
So final code should be like this:
data class SomeDataClass #JvmOverloads constructor(
var topic: String = "",
var message: String = ""
)
If you wish to use kotlin data classes without any modifications to match a Java POJO (i.e: no default/null values required and keep using val).
You can either:
provide a custom Kryo serializer to serialize it using Protobuf or Avro (or the tool and format of your choice).
use a Kotlin friendly type serializer that will serialize your data class similarly to a case class.

How to manually set a single bean property with Jackson?

I'm trying to manually update a bean (it is an argument to a JAX-RS resource method). The value of the field to be set in the bean is to be deserialized from JSON, contextually.
I want to do:
ObjectMapper objectMapper = new ObjectMapper();
// .... <configured, ClassIntrospector obtained for type> ...
BeanProperty prop;
// ... <bean property resolved through ClassIntrospector> ...
AnnotatedMember mutator = prop.getMutator();
JsonFactory jf = new JsonFactory();
JsonParser parser = jf.createParser(textProp);
Object value = objectMapper.getDeserializationContext().readValue(parser, mutator.getRawType());
mutator.setValue(beanInstance, value);
The problem is that Jackson is throwing a NullPointerException:
at com.fasterxml.jackson.databind.DeserializationContext.getTypeFactory(DeserializationContext.java:251)
at com.fasterxml.jackson.databind.DeserializationContext.readValue(DeserializationContext.java:758)
I confirmed with the debugger that the _config field of my DeserializationContext is null, and that this is what is being accessed during my code sequence.
So, what gives? How can I configure this properly so that this works? (Or is there some other way to manually deserialize a JSON fragment to a given type, respecting the JAX-RS resource context / classes?)

Why does Json.NET not include $type for the root object when TypeNameHandling is Auto?

When I set Json.NET to serialize with TypeNameHandling set to TypeNameHandling.Auto, it correctly sets $type for child properties of an object but does not do so for the root object being serialized. Why?
Please consider the following repro:
public class Animal
{
public Animal[] Offspring { get; set; }
}
public class Dog : Animal {}
Animal fido = new Dog
{
Offspring = new Animal[] { new Dog() }
};
var json = JsonConvert.SerializeObject(fido,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
The Json emitted into the json variable is:
{
"Offspring": [{
"$type": "MyApp.Dog, MyApp",
"Offspring": null
}]
}
The Json.NET Documentation says that for TypeNameHandling.Auto the behavior is:
Include the .NET type name when the type of the object being serialized is not the same as its declared type.
My question is - Why does fido not have
"$type": "MyApp.Dog, MyApp", like its puppy? :)
UPDATE: I've found out from the accepted answer to this question that I can force $type to be added by doing this:
var json = JsonConvert.SerializeObject(fido,
typeof(Animal),
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Formatting = Formatting.Indented
});
But my question still holds - Why does Json.NET not do this by itself as per the documentation?
Short answer: it doesn't because it can't.
As you stated in your question, setting TypeNameHandling to Auto directs Json.Net to include the .NET type name when the actual (run-time) type of the object being serialized is not the same as its declared (compile-time) type. In order to do that, Json.Net needs to know both types for every object.
For everything inside the root object, this is straightforward: just get the runtime type of the root object via GetType(), then use reflection to get all of its declared properties and their types, and for each one compare the declared type to the actual type to see if they differ. If they do, output the type name.
But for the root object itself, Json.Net doesn't have access to both types. All the information it has is the object referenced by fido, whose runtime type is Dog. There's no way for Json.Net to discover that the fido variable was declared as Animal, unless you provide that context somehow. And that is exactly why Json.Net provides overloads of SerializeObject which allow you to specify the compile-time type of the object being serialized. You must use one of these overloads if you want the TypeNameHandling.Auto setting to work for the root object.
Brian is absolutely correct, Json.NET has no way of knowing the compile-time declared type of the object it's being passed as the value parameter is declared as an object. The easy fix for this was if Json.NET added generic serialize methods so that the compile-time declared type would automatically flow over to Json.NET but the library's author has decided against my proposal for this here.
As an alternative, I've wrapped all my json (de)serialization needs in a JsonHelper class with generic serialize methods which use the typeof expression to automatically pass the compile-time declared type of the value to be serialized.
Newer versions of Json.Net allow you to pass the expected type to the serialize method
ser.Serialize(stream, rootObject, typeof(BaseClass));
You can pass the base class to the serialize method and TypeNameHandling.Auto will write the $type if the object and expected type do not match.

Jackson unrecognized field exception but field is in JSON

I am using Spring Integration to consume a message with a JSON Payload.
In my spring context I have
<integration:channel id="jsonToMyMessageConverterChannel"/>
<integration:json-to-object-transformer
type="com.acme.messaging.message.MyMessage"
input-channel="jsonToMyMessageConverterChannel"
output-channel="myMessageUpdateChannel"/>
My message related objects are:
MyMessage.java
#JsonIgnoreProperties(ignoreUnknown=true)
public class MyMessage {
#JsonProperty
private String timestamp;
#JsonProperty("msgs")
private List<Message> messages;
// Getters and Setters...
}
Message.java
#JsonIgnoreProperties(ignoreUnknown=true)
public class Message {
#JsonProperty
private Integer msgId;
#JsonProperty("msgText")
private String text;
// Getters and Setters...
}
When the json transformer attempts to convert the message to an object it fails with
Caused by: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "msgs" (Class com.acme.messaging.message.MyMessage), not marked as ignorable
The JSON payload definitely has msgs which is an array that has objects which represent the Message.java class.
Can any one suggest reasons why the exception occurs given that the JSON has the field that is being complained about and the class itself is also annotated to ignore unknown fields?
Update
After some debugging it looks like the #JsonProperty("msgs") annotations aren't being use, for some reason.
This works fine for me...
#Test
public void test() throws Exception {
MyMessages mm = new MyMessages();
MyMessage m = new MyMessage();
m.setMsgId(1);
m.setText("foo");
mm.setMessages(Arrays.asList(m));
mm.setTimestamp("123");
#SuppressWarnings("deprecation")
ObjectToJsonTransformer otjt = new ObjectToJsonTransformer(new ObjectMapper());
Message<?> message = new GenericMessage<MyMessages>(mm);
message = otjt.transform(message);
System.out.println(message);
#SuppressWarnings("deprecation")
JsonToObjectTransformer<MyMessages> jtot = new JsonToObjectTransformer<MyMessages>(MyMessages.class, new ObjectMapper());
message = jtot.transform(message);
mm = (MyMessages) message.getPayload();
System.out.println(mm.getTimestamp());
System.out.println(mm.getMessages().get(0).getText());
}
(I changed your classnames slightly to avoid colliding with Message<?>)
Resulting in...
[Payload={"timestamp":"123","msgs":[{"msgId":1,"msgText":"foo"}]}][Headers={timestamp=1373997151738, id=f2425f36-a500-4aee-93a4-e7e0240ce0f1, content-type=application/json}]
123
foo
Do you have both jackson 1.x (codehaus) and 2.x (fasterxml) on the classpath, and using Spring Integration 3.0.0?
If they're both on the classpath, SI will use Jackson 2.x, by default, (which won't understand 1.x annotations).
Or, I guess - are you using Jackson2 (fasterxml) annotations? Spring Integration 2.x uses Jackson 1 (codehaus).
EDIT:
In order to support both versions of Jackson, you can annotate the class with both annotations...
#JsonProperty("msgs")
#com.fasterxml.jackson.annotation.JsonProperty("msgs")
public List<MyMessage> messages;