I have class as follows
class XYZ {
private String type;
private TypeSpecific typeSpecific;
public TypeSpecific getTypeSpecific() {
return typeSpecific;
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = ATypeSpecific.class, name = "a")
})
public void setTypeSpecific(TypeSpecific typeSpecific) {
this.typeSpecific = typeSpecific;
}
}
Class ATypeSpecific extends TypeSpecific.
I want to deserialize JSON
{"type":"b"}
where typeSpecific will be set as null in object. But I am getting following exception:
com.fasterxml.jackson.databind.JsonMappingException: Missing property 'typeSpecific' for external type id 'type'
How do I deserialize above mentioned JSON into the object?
Dependency versions:
jackson-annotations: 2.7.0,
jackson-core: 2.7.4,
jackson-databind: 2.7.4
I have tried with latest patch, i.e. 2.7.9 and latest version 2.8.6. But it is not working.
Please let me know if this is possible. Thanks in advance.
In my case, having:
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, property = "#c")
class CommonReq{};
class AReq extend CommonReq{};
In spring mvc, use #RequestBody AReq to receive json http body, i got this error like missing subtype,
and I add #JsonTypeInfo(use = JsonTypeInfo.Id.NONE) annotation for AReq, finally fixed this problem.
Hope it helps.
You need to set following property to make it run:
// for version 1.x
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// for newer versions
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
Try:
#JsonProperty
private Optional<TypeSpecific> typeSpecific;
You can try this:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type",
defaultImpl = NoClass.class)
Related
I am trying to serialize an object to json using jackson 2.6.3
I want to include type info in the serialized json. This does not work for members nested within this class.
Here is the test code.
public class Test {
#JsonSubTypes({ #JsonSubTypes.Type(value = ConcreteA.class)})
interface A {
}
#JsonTypeInfo( use=JsonTypeInfo.Id.CLASS)
class ConcreteA implements A {
}
#JsonSubTypes({ #JsonSubTypes.Type(value = ConcreteB.class)})
interface B {
}
#JsonTypeInfo( use=JsonTypeInfo.Id.CLASS)
class ConcreteB implements B {
A a;
public A getA() {
return a=new ConcreteA();
}
}
#org.junit.Test
public void testSerialisation() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
System.out.println(objectMapper.writeValueAsString(new ConcreteB()));
}
}
The json being converted by jackson is
{"#class":"Test$ConcreteB","a":{}}
Note that it does not include type info for field 'a'. The type info does get included while serializing just A.
{"#class":"Test$ConcreteA"}
UPDATE:
Here is the explanation and solution from jackson dev for the problem
https://github.com/FasterXML/jackson-databind/issues/1015
It seems you were pretty close, all you need to add is a way to tell jackson how to handle your interface type.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
#JsonSubTypes({
#JsonSubTypes.Type(value = ConcreteA.class)})
interface A {
}
So adding the #JsonTypeInfo to your interface will add the type information in a property you can configure to be the one you expect, in this case I chose #class.
The Json output I got from my example:
{"#class":"com.company.jackson.Test$ConcreteB","a":{"#type":"Test$ConcreteA"}}
Hope it helps,
José Luis
You also have to set the visible property to true, otherwise it might still not work(my case). I will enhance one of the above answers:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "#class",
visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(value = ConcreteA.class)})
interface A {
}
I have faced the same issue.
I had imported wrong ObjectMapper class.
Ideally, it should be jackson-databind.jar
com.fasterxml.jackson.databind.ObjectMapper
but I have imported jackson-mapper-asl.jar
org.codehaus.jackson.map.ObjectMapper
I have this response:
{
"id":"decaa828741611e58bcffeff819cdc9f",
"statement":"question statement",
"exercise_type":"QUESTION"
}
Then, based on exercise_type attribute, I want to instantiate different objects instances (subclasses of ExerciseResponseDTO), so I create this mix in:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "exercise_type")
#JsonSubTypes({
#Type(value = ExerciseChoiceResponseDTO.class, name = "CHOICE"),
#Type(value = ExerciseQuestionResponseDTO.class, name = "QUESTION")})
public abstract class ExerciseMixIn
{}
public abstract class ExerciseResponseDTO {
private String id;
private String statement;
#JsonProperty(value = "exercise_type") private String exerciseType;
// Getters and setters
}
public class ExerciseQuestionResponseDTO
extends ExerciseResponseDTO {}
public class ExerciseChoiceResponseDTO
extends ExerciseResponseDTO {}
So I create my ObjectMapper as follows
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(ExerciseResponseDTO.class, ExerciseMixIn.class);
My test:
ExerciseResponseDTO exercise = mapper.readValue(serviceResponse, ExerciseResponseDTO.class)
Assert.assertTrue(exercise.getClass() == ExerciseQuestionResponseDTO.class); // OK
Assert.assertEquals("decaa828741611e58bcffeff819cdc9f" exercise.getId()); // OK
Assert.assertEquals("question statement", exercise.getStatement()); // OK
Assert.assertEquals("QUESTION", exercise.getExerciseType()); // FAIL. Expected: "QUESTION", actual: null
The problem is that, for some reason, the exercise_type attribute being used as property on #JsonTypeInfo is being mapped as null.
Any idea how i can solve this?
Finally, I've found the solution in the API Doc
Note on visibility of type identifier: by default, deserialization
(use during reading of JSON) of type identifier is completely handled
by Jackson, and is not passed to deserializers. However, if so
desired, it is possible to define property visible = true in which
case property will be passed as-is to deserializers (and set via
setter or field) on deserialization.
So the solution was simply adding the 'visible' attribute as follows
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "exercise_type",
visible = true)
#JsonSubTypes({
#Type(value = ExerciseChoiceResponseDTO.class, name = "CHOICE"),
#Type(value = ExerciseQuestionResponseDTO.class, name = "QUESTION")})
public abstract class ExerciseMixIn
{}
As per #jscherman answer by setting, 'visible' true in JsonTypeInfo will help in accessing exercise_type as a field.
If you use the same class to serialize also then resulting JSON will have exercise_type appear twice. So it's better to also update include to JsonTypeInfo.As.EXISTING_PROPERTY
And it's also worth looking at all other options for include.
By no means am I a Jackon/JSON wizard, which is probably evident from the following issue I'm running into:
I have 2 possible data structures I'm receiving.
The first one is called amountTransaction:
{
"amountTransaction": {
"clientCorrelator":"54321",
"endUserId":"tel:+16309700001"
}
}
Which is represented by the following Java object:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...
}
However the amountTransaction object also appears as child element of the paymentTransactionNotification object:
{
"paymentTransactionNotification": {
"amountTransaction": {
"clientCorrelator": "54321",
"endUserId": "tel:+16309700001"
}
}
}
..which I thought would be represented by:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
...
}
Parsing the JSON with the amountTransaction object alone works fine. It's a pretty straightforward example of a WRAPPER_OBJECT.
However when trying to parse the JSON for the paymentTransactionNotification, I'm getting an exception indicating that it can't properly deal with the amountTransaction as element of the paymentTransactionNotification:
com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id 'clientCorrelator' into a subtype of [simple type, class com.sf.oneapi.pojos.AmountTransaction]
Any thoughts on how I can properly annotate this so my code can properly deal with both stand alone, as well as encapsulated amountTransaction objects?
By default wrapping root node in Jackson is disabled. You can wrap inner objects but if you want to wrap root node you need to enable jackson feature for it (https://jira.codehaus.org/browse/JACKSON-747):
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
objectMapper.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
When you enabled these features you already said Jackson to wrap the root element and you don't need #JsonTypeInfo and #JsonTypeName anymore. You can simple delete them. But now you need to customize the root node name and you can use #JsonRootName for it. Your classes should look like this:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...............
}
And
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
.............
}
I've tried and Jackson converted both JSON requests as expected.
I am using Jackson's ObjectMapper to deserialize a JSON representation of an object that contains an interface as one of its properties. A simplified version of the code can be seen here:
https://gist.github.com/sscovil/8735923
Basically, I have a class Asset with two properties: type and properties. The JSON model looks like this:
{
"type": "document",
"properties": {
"source": "foo",
"proxy": "bar"
}
}
The properties property is defined as an interface called AssetProperties, and I have several classes that implement it (e.g. DocumentAssetProperties, ImageAssetProperties). The idea is that image files have different properties (height, width) than document files, etc.
I've worked off of the examples in this article, read through docs and questions here on SO and beyond, and experimented with different configurations in the #JsonTypeInfo annotation parameters, but haven't been able to crack this nut. Any help would be greatly appreciated.
Most recently, the exception I'm getting is this:
java.lang.AssertionError: Could not deserialize JSON.
...
Caused by: org.codehaus.jackson.map.JsonMappingException: Could not resolve type id 'source' into a subtype of [simple type, class AssetProperties]
Thanks in advance!
SOLUTION:
With many thanks to #Michał Ziober, I was able to resolve this issue. I was also able to use an Enum as a type id, which took a bit of Googling. Here is an updated Gist with working code:
https://gist.github.com/sscovil/8788339
You should use JsonTypeInfo.As.EXTERNAL_PROPERTY instead of JsonTypeInfo.As.PROPERTY. In this scenario your Asset class should look like this:
class Asset {
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = ImageAssetProperties.class, name = "image"),
#JsonSubTypes.Type(value = DocumentAssetProperties.class, name = "document") })
private AssetProperties properties;
public AssetProperties getProperties() {
return properties;
}
public void setProperties(AssetProperties properties) {
this.properties = properties;
}
#Override
public String toString() {
return "Asset [properties("+properties.getClass().getSimpleName()+")=" + properties + "]";
}
}
See also my answer in this question: Jackson JsonTypeInfo.As.EXTERNAL_PROPERTY doesn't work as expected.
I am trying to serialize/deserialize the following
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = IdBundleCombine.class),
#JsonSubTypes.Type(value = IdBundleDistinct.class) })
public abstract class IdBundle
{
String sharedId;
Long internalId;
//getters
}
public class IdBundleCombine extends IdBundle
{
//setters
}
public class IdBundleDistinct extends IdBundle
{
//setters
}
with the following code
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(new File("foo.json"), someInstanceOfIdBundle);
Which produce the following (without type information as you can see):
{"sharedId":"foobar","internalId":1234}
So i get an error missing property '#type' that is to contain type id when I try to deserialize it.
I tried every combination of parameters for #JsonTypeInfo and #JsonSubTypes I could find without ever succeeding in getting the type information to show in my file. I also tried to play with the #JsonTypeName on the subType without results.
My only guess is that I am doing something wrong with the mapper, but I can't find anything on the subject since most of the people seem to either don't want the type information to show up in the json string, or to have problems with the deserialization process.
I did try using the following annotation and it worked, even with the property tag.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "#type")
Add 'name' attribute to #Type for sub types and give 'property' attribute of #JsonTypeInfo any value of your choice. Class below
<!-- language: java -->
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "meta-type")
#JsonSubTypes({#Type(value = IdBundleCombine.class, name = "bundle-combine"),
#Type(value = IdBundleDistinct.class, name = "bundle-distinct")})
public abstract class IdBundle{
}
will produce following json in case it's IdBundleCombine
{"meta-type": "bundle-combine", "sharedId":"foobar","internalId":1234}