I am using a class from another module in my request.
public class KeyInput {
#NotNull
private Long id;
#NotNull
private String startValue;
#NotNull
private String endValue;
}
I cannot put #JsonIgnoreProperties(ignoreUnknown = true) annotation on this class, since the module does not contain jackson library.
Putting it on field level where I used it on my request class didn't work out.
#JsonIgnoreProperties(ignoreUnknown = true)
private List<KeyInput> keys;
Here is the incoming request. Notice the source of the problem, the two fields (name and type), which are not declared in KeyInput class.
{
"id": 166,
"name": "inceptionDate",
"type": "DATE",
"startValue": "22",
"endValue": "24"
}
How am I supposed to tell the jackson to ignore the unknown fields if the class is not in my package?
P.S: I know I can get the keys as json string and serialize it using ObjectMapper (by setting the configuration DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES to false), but I am looking for a cleaner solution here.
Also putting those fields in my class and never use it, is another dirty solution.
Two ways come to my mind.
Method 1
Create an empty child class that inherit from KeyInput class. This is the easiest method.
#JsonIgnoreProperties(ignoreUnknown = true)
public class InheritedKeyInput extends KeyInput{}
Method 2
Create a custom de-serializer for KeyInput class.
public class KeyInputDeserializer extends JsonDeserializer<KeyInput> {
#Override
public KeyInput deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
KeyInput keyInput = new KeyInput();
keyInput.setId(node.get("id").asLong());
keyInput.setEndValue(node.get("startValue").textValue());
keyInput.setStartValue(node.get("startValue").textValue());
return keyInput;
}
}
Bind this de-serializer to KeyInput using a configuration class
#Configuration
public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.failOnEmptyBeans(false)
.deserializerByType(KeyInput.class, new KeyInputDeserializer());
}
}
just simple add on above filed which you wanna ignore #JsonIgnore
Related
I'm trying to deserialize this json data into list of objects:
[{
"a": {
"commonField": 1,
"aField": "AAA"
}
}, {
"b": {
"commonField": 2,
"bField": "BBB"
}
}]
Each object may be one of several types having both common and unique fields. Information about exact shape of an object is stored in json as key in the wrapper object.
I created corresponding classes for every known shape (set of unique fields) extending class containing all common fields. Also, I added Jackson annotations to the classes to enable polymorphic deserialization. Simplified, resulting classes look like this:
#JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(KeyBasedSubTypeA.class),
#JsonSubTypes.Type(KeyBasedSubTypeB.class)
})
public abstract class KeyBasedSuperType {
public String type;
public int commonField;
}
#JsonTypeName("a")
public class KeyBasedSubTypeA extends KeyBasedSuperType {
public String aField;
}
#JsonTypeName("b")
public class KeyBasedSubTypeB extends KeyBasedSuperType {
public String bField;
}
With this setup Jackson works almost perfectly. It is able to choose correct subtype during deserialization and populate all the fields including common and unique. However, the type field is not updated by Jackson, the key value used for selecting subtype is not stored anywhere. In other words, the data is deserialized into following structure:
[KeyBasedSubTypeA { type=null; commonField=1; aField=AAA },
KeyBasedSubTypeB { type=null; commonField=2; bField=BBB }]
Note type field having null value. So, the question is - How can I make Jackson to store wrapper's key used for selecting subtype somewhere in resulting object?
Here is my JUnit test for the process
public class PolymorphicTest {
private static ObjectMapper mapper;
#BeforeClass
public static void init() {
mapper = new ObjectMapper();
}
#Test
public void testKeyDenominator() throws IOException {
TypeReference<List<KeyBasedSuperType>> dataShape =
new TypeReference<List<KeyBasedSuperType>>() {};
List<KeyBasedSuperType> result = mapper.readValue(
PolymorphicTest.class.getResourceAsStream("polymorphic-key.json"), dataShape);
assertEquals(2, result.size());
assertEquals(KeyBasedSubTypeA.class, result.get(0).getClass());
assertEquals(KeyBasedSubTypeB.class, result.get(1).getClass());
assertEquals(1, result.get(0).commonField);
assertEquals(2, result.get(1).commonField);
assertEquals("a", result.get(0).type); // <---- this line fails
assertEquals("b", result.get(1).type); // <---- this line fails
assertEquals("AAA", ((KeyBasedSubTypeA) result.get(0)).aField);
assertEquals("BBB", ((KeyBasedSubTypeB) result.get(1)).bField);
}
}
The solution actually was very close, just missed a tiny step forward. It is #JsonTypeInfo(visible=true) required to make Jackson handle type info as normal property.
#JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT, property = "type", visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(KeyBasedSubTypeA.class),
#JsonSubTypes.Type(KeyBasedSubTypeB.class)
})
public abstract class KeyBasedSuperType {
public String type;
public int commonField;
}
I'm working on a project I didn't initially create, in which the data was stored in-memory. I'm curently moving this data into the database. I'm doing this using hibernate and tapestry JPA. At some point in the project Jackson Deserialization is used (actually in connection with a UI, but I doubt that's relevant), via the #JsonDeserialize annotation, with a deserializer class (let's call it DefinitionDeserializer). DefinitionDeserializer then creates an instance of a POJO representation (let's call it Definition) of a database table (D_DEFINITION). However, D_DEFINITION has a connection to another table (D_TYPE) (and hence another POJO (PeriodType)). To resolve this connection, I'm using a tapestry service (ConnectingService), which I usually inject via the #Inject annotation. However, I can't use this method of injection when the object (in which I'm trying to inject the service, i.e. DefinitionDeserializer) was created via the new keyword - which seems to be the case for the #JsonDeserialize annotation. I also can't use ConnectingService without injecting it via the #Inject keyword, because then I couldn't inject any other services in ConnectingService either, which I'm currently doing.
I'm hoping this description didn't confuse you too much, I can't share the actual code with you and I don't think a minimal example would be much better, as it's quite a complicated case and wouldn't be such a small piece of code. If you need one, however, I can try to provide one.
Basically what I need is a way to tell JsonDeserialize to take a tapestry service instead of creating an instance of DefinitionDeserializer itself.
Edit: The classes as examples:
public DefinitionDeserializer extends StdDeserializer<Definition> {
private static final long serialVersionUID = 1L;
//TODO: The injection doesn't work yet
#Inject
private ConnectingService connectingService;
public DefinitionDeserializer() {
this(null);
}
public DefinitionDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Definition deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Definition pd = new Definition();
JsonNode node = p.getCodec().readTree(p);
if (node.has("type"))
pd.setType(periodTypeDao.findByValue("PeriodType." + node.get("type").asText()));
return pd;
}
}
#Entity
#Table(name = Definition.TABLE_NAME)
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region =
JpaEntityModelConstants.CACHE_REGION_ADMINISTRATION)
public class Definition {
public static final String TABLE_NAME = "D_DEFINITION";
private static final long serialVersionUID = 389511526676381027L;
#Id
#SequenceGenerator(name = JpaEntityModelConstants.SEQUENCE_NAME, sequenceName = JpaEntityModelConstants.SEQUENCE_NAME, initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = JpaEntityModelConstants.SEQUENCE_NAME)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "FK_TYPE", referencedColumnName = "ID")}
)
private PeriodType type;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public PeriodType getType() {
return type;
}
public void setType(PeriodType dpmType) {
this.type = dpmType;
}
//More columns
}
PeriodType looks pretty much the same as Definition.
//BaseService contains all the standard methods for tapestry JPA services
public interface ConnectingService extends BaseService<PeriodType> {
}
public class ConnectingServiceImpl extends BaseServiceImpl<PeriodType> implements ConnectingService {
public ConnectingServiceImpl() {
super (PeriodType.class);
}
}
Currently I'm using it like this (which doesn't work):
#JsonDeserialize(using = DefinitionDeserializer.class)
#JsonSerialize(using = DefinitionSerializer.class)
private Definition definition;
#JsonDeserialize doesn't create instances of deserialisers, it's just a hint for ObjectMapper to know which class to use when deserialising.
By default ObjectMapper uses Class.newInstance() for instantiating deserialisers, but you can specify custom HandlerInstantiator (ObjectMapper#setHandlerInstantiator()) in which you can use Tapestry's ObjectLocator to get instances of deserialisers, i.e. using ObjectLocator#autobuild(), or use ObjectLocator#getService() if your deserialisers are Tapestry services themselves.
Update:
public class MyHandlerInstantiator extends HandlerInstantiator
{
private final ObjectLocator objectLocator;
public MyHandlerInstantiator(ObjectLocator objectLocator)
{
this.objectLocator = objectLocator;
}
#Override
public JsonDeserializer<?> deserializerInstance(
DeserializationConfig config, Annotated annotated, Class<?> deserClass)
{
// If null returned here instance will be created via reflection
// you can always use objectLocator, or use it conditionally
// just for some classes
return objectLocator.autobuild(deserClass);
}
// Other method overrides can return null
}
then later when you're configuring ObjectMapper use #Injected instance of ObjectLocator to create an instance of custom HandlerInstantiator, i.e.:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setHandlerInstantiator(new MyHandlerInstantiator(objectLocator));
return objectMapper;
When I want to deserialize an Entity with a polymorph member, Jackson throws a com.fasterxml.jackson.databind.JsonMappingException, complaining about a missing type info (...which is actually present in the JSON -> see example).
Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '#class' that is to contain type id (for class demo.animal.Animal)\n at [Source: N/A; line: -1, column: -1] (through reference chain: demo.home.Home[\"pet\"])"
All actual work is done by a PagingAndSortingRepository from Spring HATEOAS.
I use spring-boot V 1.2.4.RELEASE, which means jackson is V 2.4.6 and Spring HATEOAS is V 0.16.0.RELEASE.
Example:
I have a pet at home:
#Entity
public class Home {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToOne(cascade = {CascadeType.ALL})
private Animal pet;
public Animal getPet() {
return pet;
}
public void setPet(Animal pet) {
this.pet = pet;
}
}
That Pet is some Animal - in this case either a Cat or a Dog. It's type is identified by the #class property...
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#class")
public abstract class Animal {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Entity
public class Cat extends Animal {
}
#Entity
public class Dog extends Animal {
}
Then there is this handy PagingAndSortingRepository, which allows me to access my home via REST/HATEOAS...
#RepositoryRestResource(collectionResourceRel = "home", path = "home")
public interface HomeRepository extends PagingAndSortingRepository<Home, Integer> {
}
To confirm all that stuff is working, I have a test in place...
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DemoApplication.class)
#WebAppConfiguration
public class HomeIntegrationTest {
#Autowired
private WebApplicationContext ctx;
private MockMvc mockMvc;
#Before
public void setUp() {
this.mockMvc = webAppContextSetup(ctx).build();
}
#Test
public void testRename() throws Exception {
// I create my home with some cat...
// http://de.wikipedia.org/wiki/Schweizerdeutsch#Wortschatz -> Büsi
MockHttpServletRequestBuilder post = post("/home/")
.content("{\"pet\": {\"#class\": \"demo.animal.Cat\", \"name\": \"Büsi\"}}");
mockMvc.perform(post).andDo(print()).andExpect(status().isCreated());
// Confirm that the POST request works nicely, so the JSON thingy is correct...
MockHttpServletRequestBuilder get1 = get("/home/").accept(MediaType.APPLICATION_JSON);
mockMvc.perform(get1).andDo(print()).andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$._embedded.home", hasSize(1)))
.andExpect(jsonPath("$._embedded.home[0].pet.name", is("Büsi")));
// Now the interesting part: let's give that poor kitty a proper name...
MockHttpServletRequestBuilder put = put("/home/1")
.content("{\"pet\": {\"#class\": \"demo.animal.Cat\", \"name\": \"Beauford\"}}");
mockMvc.perform(put).andDo(print()).andExpect(status().isNoContent());
// PUT will thow JsonMappingException exception about an missing "#class"...
MockHttpServletRequestBuilder get2 = get("/home/").accept(MediaType.APPLICATION_JSON);
mockMvc.perform(get2).andDo(print()).andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$._embedded.home", hasSize(1)))
.andExpect(jsonPath("$._embedded.home[0].pet.name", is("Beaufort")));
}
}
Interestingly I can create my home with the cat as a pet, but when I want to update the name of the cat it cannot deserialize the JSON anymore...
Any suggestions?
I'm going to attempt a half-answer.
When processing a PUT (probably PATCH as well), spring-data-rest-webmvc merges the given JSON data into the existing entity. While doing so, it strips all properties that don't exist in the entity from the JSON tree before passing it to the Jackson ObjectMapper. In other words, your #class property is gone by the time Jackson gets to deserialize your object.
You can work around this (for testing/demonstration purposes) by adding your #class property as an actual property to your entity (you have to rename it of course, say classname). Now everything will work fine, however your entity now has an otherwise useless classname property, which is probably not what you want.
Using the #JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.WRAPPER_OBJECT) approach also won't work, for a similar reason (except this time the entire wrapper object is removed). Also as with the original approach, GET and POST will work fine.
The whole thing looks like a bug or #JsonTypeInfo not supported in spring-data-rest-webmvc situation.
Maybe somebody else can shed some more light on this.
Saying I have an interface A, I want to use custom deserializer for all classes implement interface A, So I use code below but it doesn't work, While CustomAserializer works.
So what should I do to deserialize all classes implement A using my custom deserializer.
Thanks.
module.addDeserializer(A.class, new CustomADeserializer());
module.addSerializer(A.class, new CustomASerializer())
It seems you forgot to annotate your implementation classes with #JsonDeserialize(using = ImplementationClazz.class) to indicate that the class should be used to deserialize the abstract class or interface.
The following is a simple example to deserialize an interface having multiple implementations using Jackson.
Here is my interface:
#JsonDeserialize(using = UserDeserializer.class)
public interface User {
}
One implementation of the interface:
#JsonDeserialize(as = ServiceUser.class)
public class ServiceUser implements User{
private String name;
private String role;
//constructor, setters & getters
Second implementation:
#JsonDeserialize(as = AdminUser.class)
public class AdminUser implements User {
private String role;
private String designation;
//constructor, setters & getters
And here is the deserializer:
public class UserDeserializer extends JsonDeserializer<User> {
#Override
public User deserialize(JsonParser jp, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = mapper.readTree(jp);
/*write your own condition*/
if (root.has("name") && root.get("name").asText().equals("XYZ")) {
return mapper.readValue(root.toString(), ServiceUser.class);
}
return mapper.readValue(root.toString(), AdminUser.class);
}
}
You may get a StackOverflowError if you don't annotate the implementation classes. All implementation classes should deserialize themselves, otherwise it will use the deserializer from the parent class which leads to a StackOverflowError.
Just in case someone need a solution to serialize and desiralize inheritance hierarchy
you can use jackson annotation in more elegant way : JsonTypeInfo and JsonSubTypes
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = ServiceUser.class, name = "service"),
#Type(value = AdminUser.class, name = "admin")
})
public interface User{
// ...
}
Lets say that I have the following classes:
public class Person {
String name;
Set<Department> departments;
}
public class Department {
String code;
String name;
}
So I want to write a custom Department deserializer in order to annotate the deparments field in the Person class to use it. Because this custom deserializer will only be used to deserialize Department objects that are inside a Person object. The problem is that my custom Department deserializer will need to have a DepartmentRepository that must be passed in the deserializer's constructor. How can I do this? Is this possible? I don't want to register the deserializer in the object mapper because it must only be used when the deparatments field from the Person class gets deserialized.
UPDATE: What I need is, apart from annotate the departments field with JsonDeserialize annotation with the parameter contentUsing = MyCustomDepartmentDeserializer.class, is a way to tell Jackson that when it creates a MyCustomDepartmentDeserializer object, it must done it by calling a constructor that receives a DepartmentRepository. The deserializer may be something like this:
public class MyCustomDepartmentDeserializer extends JsonDeserializer<Department> {
private final DepartmentRepository departmentRepository;
public MyCustomDepartmentDeserializer(DepartmentRepository departmentRepository) {
this.departmentRepository = departmentRepository;
}
#Override
public Department deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
//IMPLEMENTATION!
}
}
First things first: to specify deserializer to use for contents of an array you can use
#JsonDeserialize(contentUsing=MyDeserializer.class)
Set<Department> departments;
to specify deserializer to use for contents of the collection in question.
As to ability to use non-default constructors, #JsonCreator allows this.
But to pass a context object, you need Jackson 1.9 may be your friend (see "Jackson 1.9 overview"), which allows "injection" of objects outside of JSON.
You can then mix and match injectable values and JSON properties, for example:
public class POJO {
#JsonCreator // can also be used for static factory methods
public POJO(#JacksonInject DepartmentRepository repo, #JsonProperty("value") int value) {
....
}
}
This might be enough to do what you are asking.
You can add a custom serializer/deserializer with a non-default constructor by registering it as a module with you ObjectMapper.
SimpleModule simpleModule = new SimpleModule();
JsonDeserializer<MyObject> customDeserializer = new CustomDeserializer("Blah");
simpleModule.addDeserializer(MyObject.class, customDeserializer);
mapper.registerModule(simpleModule);
You should also remove the annotation in the MyObject class if it's there.
Here is a deserializer I just wrote. Note the use of a non-default constructor.
public class SparseStringArrayVectorDeserializer extends JsonDeserializer<SparseStringArrayVector> {
#Override
public SparseStringArrayVector deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
/* This isn't the most efficient way to do this, since we're building a tree of nodes that we will discard.
* However, we need to change the order around, so something like this is hard to avoid.
*/
JsonNode tree = jp.readValueAsTree();
int tokenCount = tree.size();
int[] indexes = new int[tokenCount];
String[][] strings = new String[tokenCount][];
Iterator<Entry<String, JsonNode>> fieldNameIt = tree.getFields();
int slot = 0;
while (fieldNameIt.hasNext()) {
Entry<String, JsonNode> entry = fieldNameIt.next();
int index = Integer.parseInt(entry.getKey());
indexes[slot] = index;
String[] thisTokenStrings = new String[entry.getValue().size()];
for (int x = 0; x < thisTokenStrings.length; x++) {
thisTokenStrings[x] = entry.getValue().get(x).getTextValue();
}
strings[slot] = thisTokenStrings;
slot++;
}
return new SparseStringArrayVector(indexes, strings);
}
}
Used with the following. Note that you could have any constructor pattern that you like when creating the deserializer and adding it to the module.
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("ResultAccess", new Version(7, 4, 0, null));
module.addDeserializer(SparseStringArrayVector.class, new SparseStringArrayVectorDeserializer());
module.addDeserializer(AbstractResultAccess.class, new ProxyAbstractResultAccessDeserializer());
mapper.registerModule(module);
No, at the very beginning, you can go without specify a custom deserializer; Jackson can detect your nested field and map them correctly, only when all the model classes implements Serializable.
So, add implements Serializable to Department and Person, and you will see Jackson works out of the box.
Just off the top of my head, I am pretty sure you can do that using the annotations in Jackson to identify which properties you want to exposure.