I am using jackson 2.2 annotation #JsonProperty with required set to true. While deserializing json file which doesn't contain that property via ObjectMapper readValue() method no exception is being thrown.
Is it supposed to work in a different way or did I missed something?
My dto class:
public class User {
public enum Gender {MALE, FEMALE}
;
public static class Name {
private String _first, _last;
public String getFirst() {
return _first;
}
public String getLast() {
return _last;
}
public void setFirst(String s) {
_first = s;
}
public void setLast(String s) {
_last = s;
}
}
private Gender _gender;
private Name _name;
private boolean _isVerified;
private byte[] _userImage;
#JsonProperty(value ="NAAME",required = true)
public Name getName() {
return _name;
}
#JsonProperty("VERIFIED")
public boolean isVerified() {
return _isVerified;
}
#JsonProperty("GENDER")
public Gender getGender() {
return _gender;
}
#JsonProperty("IMG")
public byte[] getUserImage() {
return _userImage;
}
#JsonProperty(value ="NAAME",required = true)
public void setName(Name n) {
_name = n;
}
#JsonProperty("VERIFIED")
public void setVerified(boolean b) {
_isVerified = b;
}
#JsonProperty("GENDER")
public void setGender(Gender g) {
_gender = g;
}
#JsonProperty("IMG")
public void setUserImage(byte[] b) {
_userImage = b;
}
}
This is how do I deserialize the class:
public class Serializer {
private ObjectMapper mapper;
public Serializer() {
mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule("PIF deserialization");
mapper.registerModule(sm);
}
public void writeUser(File filename, User user) throws IOException {
mapper.writeValue(filename, user);
}
public User readUser(File filename) throws IOException {
return mapper.readValue(filename, User.class);
}
}
This is how it is actually called:
Serializer serializer = new Serializer();
User result = serializer.readUser(new File("user.json"));
Actuall json looks like:
{"GENDER":"FEMALE","VERIFIED":true,"IMG":"AQ8="}
I would expect that since _name is not specified in json file and is required that the exception will be thrown.
With Jackson 2.6 you can use required, however you have to do it using JsonCreator
For example:
public class MyClass {
#JsonCreator
public MyClass(#JsonProperty(value = "x", required = true) Integer x, #JsonProperty(value = "value_y", required = true) Integer y) {
this.x = x;
this.y = y;
}
private Integer x;
private Integer y;
}
If x or y are not present an exception will be thrown when trying to deserialize it.
As per Jackson annotations javadocs: "Note that as of 2.0, this property is NOT used by BeanDeserializer: support is expected to be added for a later minor version."
That is: no validation is performed using this settings. It is only (currently) used for generating JSON Schema, or by custom code.
Related
I use #JsonSerialize to convert the enum class to Integer, and the writing is successful; but each enum class must write a converted class, is there a way to write only one conversion class?
I tried to use generics to get the type of the enum class, but failed, this is not allowed
// error code
#JsonSerialize(using = StatusSerializer<StatusEnum>.class)
private Integer status;
#Data
public class ZkUser {
/**
* name
*/
private String name;
/**
* status
*/
#JsonSerialize(using = StatusSerializer.class)
private Integer status;
}
//==========================================================================================
public enum StatusEnum {
// d
ON(1),
OFF(0);
private final Integer code;
public static StatusEnum getEnumByCode(Integer code) {
for (StatusEnum s : values()) {
if (s.code.equals(code)) {
return s;
}
}
return null;
}
StatusEnum(Integer code) {
this.code = code;
}
public Integer getCode() {
return code;
}
}
//=========================================================================================
public class StatusSerializer<T> extends JsonSerializer<Integer> {
private T t;
#Override
public void serialize(Integer value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
var b = StatusEnum.getEnumByCode(value);
jsonGenerator.writeObject(b);
}
}
You can both serialize and deserialize an enum by adding the #JsonValue annotation (see this answer). The following example is based on your enum:
public class Main {
public static enum StatusEnum {
ON(1),
OFF(0);
private final Integer code;
StatusEnum(Integer code) {
this.code = code;
}
#JsonValue
public Integer getCode() {
return code;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = objectMapper.writeValueAsString(StatusEnum.ON);
System.out.println(jsonString);
StatusEnum readEnum = objectMapper.readValue(jsonString, StatusEnum.class);
System.out.println(readEnum);
}
}
The program outputs:
1
ON
I have a problem related to:
De-serializing JSON to polymorphic object model using Spring and JsonTypeInfo annotation
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id '[' as a subtype
The solutions provided there didn't work for me.
I have the following DTO:
public class QuestionaireAnswersDTO {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = As.EXISTING_PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(name = "single", value = SingleChoiceAnswerDTO.class),
#JsonSubTypes.Type(name = "multi", value = MultipleChoiceAnswerDTO.class)
})
public static abstract class QuestionaireAnswerDTO {
String answerId;
String name;
public String getAnswerId() {
return answerId;
}
public void setAnswerId(String answerId) {
this.answerId = answerId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
String questionnaireId;
List<QuestionaireAnswerDTO> answers;
public String getQuestionnaireId() {
return questionnaireId;
}
public void setQuestionnaireId(String questionnaireId) {
this.questionnaireId = questionnaireId;
}
public List<QuestionaireAnswerDTO> getAnswers() {
return answers;
}
public void setAnswers(List<QuestionaireAnswerDTO> answers) {
this.answers = answers;
}
with those subclasses:
public static class SingleChoiceAnswerDTO extends QuestionaireAnswerDTO {
#Nullable
String selectedOption;
public String getSelectedOption() {
return selectedOption;
}
public void setSelectedOption(String selectedOption) {
this.selectedOption = selectedOption;
}
}
public static class MultipleChoiceAnswerDTO extends QuestionaireAnswerDTO {
List<String> selectedOptions;
public List<String> getSelectedOptions() {
return selectedOptions;
}
public void setSelectedOptions(List<String> selectedOptions) {
this.selectedOptions = selectedOptions;
}
}
Now I wanted to write a test using this json object:
{
"questionnaireId":"questionnaire1",
"answers":[
{
"name":"single",
"answerId":"Question1",
"selectedOption":"Yes"
},
{
"name":"multi",
"answerId":"Question3",
"selectedOptions":[
"yes",
"no"
]
}
]
}
Using this test:
JsonFactory factory = new JsonFactory();
factory.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
ObjectMapper mapper = new ObjectMapper(factory);
mapper.registerSubtypes(QuestionaireAnswersDTO.SingleChoiceAnswerDTO.class, QuestionaireAnswersDTO.MultipleChoiceAnswerDTO.class);
QuestionaireAnswersDTO result = mapper.readValue(testData, QuestionaireAnswersDTO.class);
String resultAsString = mapper.writeValueAsString(result);
System.out.println(resultAsString);
Which results in the following Error:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, (...)
missing type id property '#class' (for POJO property 'answers')
Using the .registerSubtypes() method instead of JsonSubtypes didn't work here instead of JsonSubtypes. Same error occurs.
How do I get Jackson to treat 'name' as if it had a #JsonProperty annotation?
public class SimpleClass {
private String name;
private String doNotSerialize;
public SimpleClass( #JsonProperty("name") String name ) {
this.name = name;
}
public String getName() {
return name;
}
public int getSum() {
return 1+1;
}
}
The way it is now, I get an error, Unrecognized field "sum", because it treats every getter as a serializable property.
If I add a class annotation:
#JsonAutoDetect( getterVisibility = JsonAutoDetect.Visibility.NONE )
I get an empty string when serializing. I was hoping that Jackson would see the #JsonProperty on the constructor parameter and figure it out.
If I change the class annotation to:
#JsonAutoDetect( getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.ANY )
Then I get the 'doNotSerialize' field included.
If I set a #JsonCreator on the constructor, and change my autodetect, I still get a blank string:
#JsonAutoDetect( getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY )
public class SimpleClass {
private String name;
private String doNotSerialize;
#JsonCreator
public SimpleClass( #JsonProperty("name") String name ) {
this.name = name;
}
public String getName() {
return name;
}
public int getSum() {
return 1+1;
}
}
What I'm hoping is that somehow I can tell Jackson to treat all the constructor parameters as serializable fields, and all other fields / setters as non-serializable.
You can use a filter to only serialise getters which have a matching field, e.g.
package org.example;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import java.io.IOException;
import java.io.StringWriter;
public class App {
#JsonFilter("test")
public static class SimpleClass {
private String name;
private String doNotSerialize;
public SimpleClass(String name ) {
this.name = name;
}
public String getName() {
return name;
}
public int getSum() {
return 1+1;
}
}
public static void main( String[] args ) throws IOException {
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("test", new SimpleBeanPropertyFilter() {
#Override
protected boolean include(BeanPropertyWriter writer) {
return super.include(writer);
}
#Override
protected boolean include(PropertyWriter writer) {
String name = writer.getName();
Class clazz = writer.getMember().getDeclaringClass();
try {
clazz.getDeclaredField(name);
return super.include(writer);
} catch (NoSuchFieldException e) {
// ignore
return false;
}
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.setFilterProvider(filterProvider);
StringWriter sw = new StringWriter();
mapper.createGenerator(sw).writeObject(new SimpleClass("foo"));
System.out.println(sw.toString());
}
}
I don't know your full requirements, but this should be a start.
I haven't tried to do what you actually, asked, that is, look at constructor parameters, but that should be possible too.
If you want "sum" to be included in the serializad json but want to ignore it when deserializing you can do:
#JsonIgnoreProperties(ignoreUnknown=true)
public class SimpleClass {
// properties/getters
public int getSum() { return 1+1; }
}
If you want to remove "sum" entirely from the json you can do
#JsonIgnoreProperties({"sum"})
public class SimpleClass {
// properties/getters
public int getSum() { return 1+1; }
}
or
public class SimpleClass {
// properties/getters
#JsonIgnore
public int getSum() { return 1+1; }
}
I want to parse a JSON document with Jackson and apply some transformation on all nodes. For example, let's say that I want all values to be in uppercase after deserialization.
The actual use case is a bit more complex:
transformation is more complex, the transformer class need to be injected with some configuration, I'd like it to be a configureable instance
transformation has to happen on all properties, I'd like to be able to not add an annotation on each property of each class deserialized.
There are enough configuration options / hooks in Jackson, so I'm fairly sure that this is possible, I just can't find my way around.
The test below shows what I'm trying to achieve:
public class JsonValueFilterTest {
private ObjectMapper mapper;
#Before
public void setupObjectMapper() {
mapper = new ObjectMapper();
// TODO: configure mapper to upper case all values
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
Entity myEntity = mapper.readValue("{\"name\":\"myName\"}", Entity.class);
assertThat(myEntity.getName()).isEqualTo("MYNAME"); // fails
}
public static class Entity {
private final String name;
#JsonCreator
public Entity(#JsonProperty("name") String name) { this.name = name; }
public String getName() { return name; }
#Override
public String toString() { return "name='" + name + "'"; }
}
}
You can use converter for that simple case to not implement custom deserializer. I don't know why, but It's not working on the creator constructors, though. So you will have to use non-final fields.
public class JsonValueFilterTest {
private ObjectMapper mapper;
#BeforeTest
public void setupObjectMapper() {
mapper = new ObjectMapper();
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
Entity myEntity = mapper.readValue("{\"name\":\"myName\"}", Entity.class);
Assert.assertEquals(myEntity.getName(), "MYNAME"); // fails
}
public static class UpCaseConverter extends StdConverter<String, String> {
public String convert(String value) {
return value==null ? null : value.toUpperCase();
}
}
public static class Entity {
private String name;
public Entity() {}
public Entity(String name) {
this.name = name;
}
#JsonDeserialize(converter = UpCaseConverter.class)
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public String toString() {
return "name='" + name + "'";
}
}
}
My final solution (thanks to Alban):
configure the ObjectMapper with a custom JsonNodeFactory which transforms all text nodes
deserialize json to JsonNode (this will apply transformation)
convert the JsonNode to my custom class
public class JsonValueFilterTest {
private ObjectMapper mapper;
#Before
public void setupObjectMapper() {
mapper = new ObjectMapper();
mapper.setNodeFactory(new JsonNodeFactory() {
#Override
public TextNode textNode(String text) {
return super.textNode(text.toUpperCase());
}
});
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
JsonNode jsonNode = mapper.readTree("{\"name\":\"myName\"}");
Entity myEntity = mapper.treeToValue(jsonNode, Entity.class);
assertThat(myEntity.getName()).isEqualTo("MYNAME");
}
public static class Entity {
private final String name;
#JsonCreator
public Entity(#JsonProperty("name") String name) { this.name = name; }
public String getName() { return name; }
#Override
public String toString() { return "name='" + name + "'"; }
}
}
I'm being given a Json file with the form:
{
"descriptions": {
"desc1": "someString",
"desc2": {"name":"someName", "val": 7.0}
}
}
I have the POJO:
public class CustomClass {
Map<String, Object> descriptions;
public static class NameVal{
String name;
double val;
public NameVal(String name, double val){...}
}
}
I can recreate the json file with the code:
CustomClass a = new CustomClass();
a.descriptions = new HashMap<String, Object>();
a.descriptions.put("desc1", "someString");
a.descriptions.put("desc2", new CustomClass.NameVal("someName", 7.0));
new ObjectMapper().writeValue(new File("testfile"), a);
But, when I read the object back in using:
CustomClass fromFile = new ObjectMapper().readValue(new File("testfile"), CustomClass.class);
then fromFile.descriptions.get("desc2") is of type LinkedHashMap instead of type CustomClass.NameVal.
How can I get Jackson to properly parse the type of the CustomClass.NameVal descriptors (other than making some class that wraps the parsing and explicitly converts the LinkedHashMap after Jackson reads the file)?
Try this. Create a class Description with name and value attributes:
public class Description {
private String name;
private double val;
}
Now in your CustomClass do this:
public class CustomClass {
List<Description> descriptions;
}
And that's it. Remember to create getters and setters because Jackson needs it.
You could try something like this:
public class DescriptionWrapper {
private Description descriptions;
public Description getDescriptions() {
return descriptions;
}
public void setDescriptions(Description descriptions) {
this.descriptions = descriptions;
}
}
public class Description {
private String desc1;
private NameValue desc2;
public String getDesc1() {
return desc1;
}
public void setDesc1(String desc1) {
this.desc1 = desc1;
}
public NameValue getDesc2() {
return desc2;
}
public void setDesc2(NameValue desc2) {
this.desc2 = desc2;
}
}
public class NameValue {
private String name;
private double val;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
}