I have a Scala case class that represents my JSON as below:
class MyJSON(x: Int, typeA: TypeA, typeB: TypeB)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes(Array(
new Type(value = classOf[ClassA], name = "ClassA"),
new Type(value = classOf[ClassB], name = "ClassB")))
trait TypeA {
...
...
}
In my ClassA, I have certain fields that are deserialized from the JSON. But I also want that if there are certain fields that are not part of my Class objects, I want them to be ignored. What I did was I used the #JsonIgnoreProperties(ignoreUnknown=true) annotation on the MyJSON class as below:
#JsonIgnoreProperties(ignoreUnknown=true)
class MyJSON(x: Int, typeA: TypeA, typeB: TypeB)
It failed when my input JSON had some unknown fields. But when I moved this annotation to one of my Class (say ClassA) in my case, it was ignored. The problem is that I do not want to add this ignore properties annotation to all my classes, but rather I add it just to the top and want that propagated to all the types.
Try this
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
I found a much elegant solution to the problem. I used Jackson MixIn mechanism to actually add the additional annotations to my target types during runtime. Here is what I did:
#JsonIgnoreProperties({ Array("field1", "field2") })
abstract class MixInAnnotations{}
In my JSONMarshaller class where I create the ObjectMapper, I do the following:
val mapper = new ObjectMapper()
mapper.registerModule(new MyApplicationModule)
The MyApplicationModule now looks like:
public class MyApplicationModule extends com.fasterxml.jackson.databind.module.SimpleModule {
public MyApplicationModule() {
super(MyApplicationModule.class.getSimpleName());
// Add the corresponding MixIn Annotations to ignore the JSON properties
setMixInAnnotation(MyTargetClass1.class, MixInAnnotations.class);
setMixInAnnotation(MyTargetTrait.class, MixInAnnotations.class);
}
}
I call the setter methods to add the MixInAnnotations to my target classes. The target classes here could also be a trait that has the JsonSubTypes Annotation. So effectively, I'm not polluting all my types with the #JsonIgnoreProperties annotation.
Related
In Spring when a controller method is annotated with #JsonView, it returns only the
respective annotated properties of the object honoring the configuration
spring.jackson.mapper.default-view-inclusion, which is set to false by default.
// Kotlin code
abstract class Base {
lateinit var transientInternalProperty: String
}
class Main(val externalProperty: String) : Base()
#RestController
#RequestMapping("/")
class MainController {
#JsonView(Views.Public::class)
#GetMapping("/")
fun index() = Main()
}
Taking the above example, how to exclude non annotated properties on the generated
Main_Public schema. How to leave transientInternalProperty out of Main_Public without having to annotate it also?
I couldn't find anything about this in the documentation. Just this small section.
I tried to annotate the class itself with #JsonView to indicate "default view" for properties but it did not work.
To exclude properties from the generated OpenAPI sepc: prefer swagger-annotation, you #Hidden or #Schema(hidden = true)).
I have a bunch of interfaces annotated as follows:
#MyAnnotation(DefaultImplementation.class)
interface SomeInterface {
...
}
and an accompanying implementation
class DefaultImplementation implements SomeInterface {
...
}
When my ObjectMapper runs into a field of type SomeInterface I would like it to deserialize it as if it was a DefaultImplementation. That is, I would like encode the following logic in the deserialization process:
if (staticTypeToDeserialize.isAnnotationPresent(MyAnnotation.class))
deserializeAs(type.getAnnotation(MyAnnotation.class).value());
else
fallBackOnRegularDeserialization();
I hope I have a complete answer for you. Jackson can do this through a MixIn. I'm no expert on that, but I did it recently so ... In essence, you stick type information into the JSON, then Jackson can use that type information to deserialize it the way you want.
So first, you need a mixin class that knows about the types you are going to serialize and deserialize.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MyString.class, name = "mystring"),
#JsonSubTypes.Type(value = MyEvent.class, name = "myevent") })
public abstract class PolymorphicMixIn {
}
The MyString and MyEvent class derive from the same base class in my case. So everything I stick in and out of Jackson is that one class type. Say that baseclass is MyBaseClass.
So then in your jackson class, you need to tell the object mapper about this.
private ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixInAnnotations(MyBaseClass.class, PolymorphicMixIn.class);
That's pretty much it. Now you can just call objectMapper.writeValueAsString and it'll write the Jackson and stick in type information.
To read it out, just pass in the class type you are expecting:
MyString outputString = objectMapper.readValue(argAsString, MyString.class);
I hope that's enough to get you where you need to be!
I am using Jackson to parse JSON that I have no control over. The JSON looks like this:
{
"status":"0"
"type":"type1"
"info": {
// additional fields
}
}
My class looks like this
public class Response {
private String status;
private String type;
private Info info
}
The subclass of Info that I use depends on the type property, so my mapping for info is
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = Type1Info.class, name = "type1"),
#JsonSubTypes.Type(value = Type2Info.class, name = "type2") })
public abstract class Info {
// some fields
}
As far as I can tell this is the correct way to use type info when the distinguishing element is at the same level as the element that has to be casted. But this doesn't work, I always get the same error:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token
(END_OBJECT), expected FIELD_NAME: missing property 'type' that is to
contain type id
If I change EXTERNAL_PROPERTY to PROPERTY I still get the same error. Is my understanding of EXTERNAL_PROPERTY wrong?
From Javadoc:
Inclusion mechanism similar to PROPERTY, except that property is
included one-level higher in hierarchy, i.e. as sibling property at
same level as JSON Object to type. Note that this choice can only be
used for properties, not for types (classes). Trying to use it for
classes will result in inclusion strategy of basic PROPERTY instead.
Noticed that can only be used for properties is bolded. Source: JsonTypeInfo.As.EXTERNAL_PROPERTY.
So, you have to move all annotation from Info class to property info or setInfo method in Response class.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes(value = { #JsonSubTypes.Type(value = Type1Info.class, name = "type1"),
#JsonSubTypes.Type(value = Type2Info.class, name = "type2") })
public void setInfo(Info info) {
this.info = info;
}
For me, you should also remove type property from Response class. It will be generated dynamically during serialization process. In deserialization you do not need it because Jackson cares about types. Your class could look like this:
class Response {
private String status;
private Info info;
//getters, setters
}
See also this question: JSON nest class data binding.
For all those who use AutoValue or any other source code generator: #JsonTypeInfo and #JsonSubTypes annotations does not work when you annotate your abstract methods (which in generated code returns the actual property).
If you do it this way, you will get following exception:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of [xxx] (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Also it not works when you annotate parameter in your method annotated as #JsonCreator. If you do it this way you would get this weird exception: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class [xxx]
So to deal with it just dont use source code generator for this class, just map the Response object using common jackson annotations: #JsonCreator, #JsonProperty and #JsonTypeInfo, #JsonSubTypes
tested with jackson 2.10.5
I have a simple hierarchy of data objects, which have to be converted to JSON format. Like this:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "documentType")
#JsonSubTypes({#Type(TranscriptionDocument.class), #Type(ArchiveDocument.class)})
public class Document{
private String documentType;
//other fields, getters/setters
}
#JsonTypeName("ARCHIVE")
public class ArchiveDocument extends Document { ... }
#JsonTypeName("TRANSCRIPTIONS")
public class TranscriptionDocument extends Document { ... }
Upon JSON parsing I encounter errors like this one:
Unexpected duplicate key:documentType at position 339. , because in the generated JSON there are actually two documentType fields.
What should be changed to make JsonTypeName value appear in documentType field, without an error (eg replacing the other value)?
Jackson version is 2.2
Your code doesn't show it, but I bet you have a getter in your Document class for the documentType property. You should annotate this getter with #JsonIgnore like so:
#JsonIgnore
public String getDocumentType() {
return documentType;
}
There is an implicit documentType property associated with each subclass, so having the same property in the parent class causes it to be serialized twice.
Another option would be to remove the getter altogether, but I assume you might need it for some business logic, so the #JsonIgnore annotation might be the best option.
In Jackson, I am using annotation #JsonTypeInfo to include polymorphism support.
If, I do not want to go with annotation based approach, I can use global default typing or override the type information handling module.
I have tried global type information but it is emitting type information for all non final type.
What I need ,
I want to include type information only for polymorphic type.
I want to change default format of type info (to key-value pair)
Is it possible to achieve above two points just by twitting global configuration?
If not, what extension point should I used used to customize type-information module ?
I have read JacksonAnnotationIntrospector is the class which deals with type info.
Should I customize it to achieve above mentioned two points?
Help with Example will be well and good.
You can use Jackson's DefaultTypeResolverBuilder for this purpose. Extend this class and override the useForType method appropriately. Here is an example that adds type information only for the classes belonging to the test.jackson package (and sub-packages):
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
public class CustomTypeResolverBuilder extends DefaultTypeResolverBuilder
{
public CustomTypeResolverBuilder()
{
super(DefaultTyping.NON_FINAL);
}
#Override
public boolean useForType(JavaType t)
{
if (t.getRawClass().getName().startsWith("test.jackson")) {
return true;
}
return false;
}
}
Now, consider that you have Foo.java in test.jackson package and Bar.java in org.myorg package, each containing an int variable called "integer" and a String variable called "string".
You can serialize objects of these two classes this way:
ObjectMapper objectMapper = new ObjectMapper();
TypeResolverBuilder<?> typeResolver = new CustomTypeResolverBuilder();
typeResolver.init(JsonTypeInfo.Id.CLASS, null);
typeResolver.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolver.typeProperty("#CLASS");
objectMapper.setDefaultTyping(typeResolver);
Foo foo = new Foo(10, "Foo");
Bar bar = new Bar(20, "Bar");
System.out.println(objectMapper.writeValueAsString(foo));
System.out.println(objectMapper.writeValueAsString(bar));
The corresponding output will be:
{"#CLASS":"test.jackson.Foo","integer":10,"string":"Foo"}
{"integer":20,"string":"Bar"}
You can also customize the name of the attribute that represents the type ("#CLASS" in the above example). Hope this helps!
You can use the Moonwlker library.
With it, you can create an ObjectMapper like this:
ObjectMapper objectMapper = new ObjectMapper();
MoonwlkerModule module =
MoonwlkerModule.builder()
.fromProperty("#CLASS").toSubclassesOf(Animal.class)
.build();
objectMapper.registerModule(module);
And then use that mapper to (de)serialize. The Moonwlker website contains more details and configuration options.