#JsonUnwrapped disables Jackson's fail_on_unknown properties - java

I'm attempting to write a simple class that will validate a JSON input string if it can be converted to a target JAVA object.
The validator should fail if any unknown field is found in the input JSON String.
It all works as expected except until I annotate the B object inside the A class with #JsonUnwrapped , then the object mapper will silently ignore the unknown properties without failing.
Here is my code :
Class A :
public class A implements Serializable{
protected String id;
protected String name;
protected #JsonUnwrapped B b;
public A(){
}
public A(String id, String name, B b) {
super();
this.id = id;
this.name = name;
this.b = b;
}
//GETTERS/SETTERS
}
Class B :
public class B {
protected String innerId;
protected String innerName;
public B(){
}
public B(String innerId, String innerName) {
super();
this.innerId = innerId;
this.innerName= innerName;
}
//GETTERS/SETTERS
}
The validator class:
public class JsonValidator{
public boolean validate(){
ObjectMapper mapper = new ObjectMapper();
//mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
try {
mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
A a = mapper.readValue(
JsonValidatorBean.class.getResourceAsStream("sample.json"),
A.class);
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
The JSON to validate :
{
"id": "aaaa",
"naome": "aaa",
"innerId" : "bbbb",
"innerName" : "bbb"
}
I'm using Jackson 2.1
I'm expecting this code to fail on the unknown property "naome" but it doesn't it just gets ignored.
If I remove the #JsonUnwrapped and adapt the Json to having an embedded object, the above code fails as expected.
Any ideas?

Yes, this is true statement. Due to logic needed to pass down unwrapped properties from parent context, there is no way to efficiently verify which properties might be legitimately mapped to child POJOs (ones being unwrapped), and which not.
There is an RFE to try to improve things, to catch unmappable properties but current versions (up to and including 2.2) can not do both unwrapping and guard against unmappable properties.

Related

Serializing and deserializing JSON targeting JAVA classes with Jackson

Trying to deserialize/serialize JSON into Java beans I've created. Really new to Jackson and this endeavor, so bear with me. I have the following:
{
"foo": {
"firstBlock": {
"myValue": 1,
"someBool": true,
"stringValue": "OK"
},
"anotherBlock": {
"values": [
{
"yikes01": 42
},
{
"yikes02": 215
}
],
"myInt": 64,
"logging": "Yes"
}
}
}
My Java beans are broken down into several as the objects in the JSON are used repeatedly, so it would be:
#JsonRootName("foo")
public class FooBean {
private FirstBlockBean firstBlock;
private AnotherBlockBean anotherBlock;
#JsonGetter("firstBlock")
public FirstBlockBean getFirstBlock() { return firstBlock; }
#JsonSetter("firstBlock")
public void setFirstBlock(FirstBlockBean firstBlock) { this.firstBlock = firstBlock; }
#JsonGetter("anotherBlock")
public AnotherBlockBean getAnotherBlock() { return anotherBlock; }
#JsonSetter("firstBlock")
public void setAnotherBlock(AnotherBlockBean anotherBlock) { this.anotherBlock = anotherBlock; }
}
#JsonRootName("firstBlock")
public class FirstBlockBean {
private int myValue;
private Boolean someBool;
private String stringValue;
#JsonGetter("myValue")
public int getMyValue() { return myValue; }
#JsonSetter("myValue")
public void setMyValue(int myValue) { this.myValue = myValue; }
#JsonGetter("someBool")
public Boolean getSomeBool() { return someBool; }
#JsonSetter("someBool")
public void setSomeBool(Boolean someBool) { this.someBool = someBool; }
#JsonGetter("stringValue")
public String getStringValue() { return stringValue; }
#JsonSetter("someBool")
public void setStringValue(String stringValue) { this.stringValue = stringValue; }
}
...and AnotherBlockBean class implemented in similar fashion (omitted for brevity.) I'm using Jackson for this, and my question is - is there a mechanism in Jackson for serializing and deserializing for this case? Ideally I'd like something along the lines of (pseudo-code below because I've not been able to surface anything via Google searches or searches on here):
// Assume "node" contains a JsonNode for the tree and foo is an uninitialized FooBean class object.
JsonHelper.deserialize(node, FooBean.class, foo);
At this point I'd be able to read the values back:
int i = foo.getFirstBlock().getMyValue();
System.out.println("i = " + i); // i = 1
Similarly I'd like to be able to take the foo instance and serialize it back into JSON with another method. Am I dreaming for wanting this sort of built-in functionality or does it exist?
The main class when working with Jackson is the ObjectMapper. It has a lot of options, take a look at the available methods.
This is an example of a typical helper class that uses the ObjectMapper to convert between Java objects and Strings.
public class JsonHelper {
private ObjectMapper objectMapper;
public JsonHelper(){
this.objectMapper = new ObjectMapper();
// Your mapping preferences here
this.objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.objectMapper.setSerializationInclusion(Include.NON_NULL);
this.objectMapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
}
public String serialize(Object object) {
try {
return this.objectMapper.writeValueAsString(object);
} catch (Exception e) {
// TODO Handle exception
return null;
}
}
public <T> T deserialize(String json, Class<T> clazz) {
try {
return this.objectMapper.readValue(json, clazz);
} catch (Exception e) {
// TODO Handle exception
return null;
}
}
public <T> T deserialize(String json, TypeReference<T> valueTypeRef) {
try {
return this.objectMapper.readValue(json, valueTypeRef);
} catch (Exception e) {
// TODO Handle exception
return null;
}
}
}
Some tips:
If the name of the getter and setter methods follows the usual convention, you can omit the #JsonGetter and #JsonSetter annotations and just use the #JsonProperty annotation in the field declaration
If the name of the java field is equal to the node name in the JSON, you can also omit the #JsonProperty annotation (Jackson will map JSON nodes and Java fields with matching names).

Using Java 8 to convert a list of String into a list of Object

There is a stream read from file, the content of each line is like:
{"uid":"5981865218","timestamp":1525309552069,"isHot":true}
The class of User:
public class User {
private String uid;
private long timestamp;
private boolean isHot;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public boolean getIsHot() {
return isHot;
}
public void setIsHot(boolean isHot) {
this.isHot = isHot;
}
}
The code that I want to get a list of Object 'List' from file stream:
BufferedReader targetBr = null;
targetBr = new BufferedReader(new FileReader(targetUsersFile));
List<User> tmpUsers = targetBr.lines().?I don't know how process in there?.collect(Collectors.toList());
You need to deserialize the String to User objects. I have used Jackson ObjectMapper here (you could very well use others like Gson).
ObjectMapper objectMapper = new ObjectMapper();
...
targetBr.lines()
.map(line -> {
try {
return objectMapper.readValue(line, User.class);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
The map part of the stream takes a Function and hence you cannot throw a checked exception from there. Hence, I have wrapped the IOException that Jackson readValue throws (could throw) into a RuntimeException. You might have to change that part according to your need.
This is just a start. Think about what to do when there is an invalid entry that cannot be deserialized into a User. Some specific corner cases:
What if there is an unrecognized field (a property that is not present in the User class) in the input string. ObjectMapper in this case throws an UnrecognizedPropertyException exception. You can find ways to ignore it.
What if one or more of fields in the User class is missing in the String... Are some/all of them mandatory...?

Convert JSON to Object with JAXB only if all fields are filled out

I'm building a RESTful web service with Jersey. I use JAXB to convert incoming JSON objects into Java objects. Unfortunately this approach allows to create Java objects which don't have all mandatory fields. If I have 3 mandatory fields but the JSON contains only 1 field, I would like to see an exception thrown.
Resource class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Resource {
private int field1;
private String field2;
private String field3;
public Resource() {
}
...
}
REST class:
#Path("resource")
public class ResourceREST {
...
#POST
#Consumes(APPLICATION_JSON)
#Produces(TEXT_PLAIN)
public String createResource(Resource resource) {
...
}
...
}
Is there any possibility to do this with JAXB? If not, how can I realize this input validation?
Thanks in advance!
I have gone through the same scenario and applied some logic to fix this after the JSON is generated.
In a List add those Field Names that you considered as mandatory.
public static final List<String> REQUIRED_FIELDS = new ArrayList<String>();
static {
REQUIRED_FIELDS.add("Field1");
REQUIRED_FIELDS.add("Field2");
};
Send those JSON that you have build to a validate method.
Your validate method should be like this.
public void validateRequiredFields(JSONObject jsonObject, List<String> requiredFields) throws ParserException, Exception {
if (log.isDebugEnabled()) {
log.debug("Entering validateForRequiredFields");
}
List<String> missingFields = new ArrayList<String>();
try {
if (requiredFields != null) {
for (String requiredField : requiredFields) {
if (ifObjectExists(jsonObject, requiredField)) {
if (StringUtils.isEmpty(jsonObject.getString(requiredField))) {
missingFields.add(requiredField);
}
} else {
missingFields.add(requiredField);
}
}
}
if (missingFields != null && missingFields.size() > 0) {
throw new Exception(missingFields);
}
} catch (JSONException e) {
throw new ParserException("Error occured in validateRequiredFields", e);
}
}

Serialize custom exception to JSON, not all fields are serialized

I'm trying to serialize a custom Exception in Java using writeValueAsString() method from Jackson library. I intend to send it by HTTP to another machine. This is working partialy because not all fields are included in JSON after serialize. The top level exception Throwable implements Serializable interface, and also has some constructors that add info about what is to be serialized. I suppose the truth is somewhere here. Please help with some advices. Here is my custom exception code:
import java.io.Serializable;
public class MyException extends RuntimeException{
private static String type = null;
private static String severity = null;
// somewhere on google I red that should use setters to make serialize work
public static void setType(String type) {
MyException.type = type;
}
public static void setSeverity(String severity) {
MyException.severity = severity;
}
public MyException(String message) {
super(message);
}
}
somewhere in code I use:
MyException exc = new MyException("Here goes my exception.");
MyException.setType(exc.getClass().getSimpleName());
MyException.setSeverity("Major");
throw exc;
and in other place I have:
ObjectMapper mapper = new ObjectMapper();
try {
responseBuilder.entity(mapper.writeValueAsString(MyException) );
}
catch (JsonGenerationException e) {e.printStackTrace(); }
catch (JsonMappingException e) {e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
The result JSON object is:
{"cause":null,"message":"Here goes my exception.","localizedMessage":"Here goes my exception.","stackTrace":[{...a usual stack trace...}]}
Here I also expect to see my type and severity fields.
I made type and severity non-static, and it seems to be working fine. I used the following code, and I see both type and severity in the serialized output.
public class MyException extends RuntimeException
{
private String type = null;
private String severity = null;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSeverity() {
return severity;
}
public void setSeverity(String severity) {
this.severity = severity;
}
public MyException(String message) {
super(message);
}
}
... and
MyException exc = new MyException("Here goes my exception.");
exc.setType(exc.getClass().getSimpleName());
exc.setSeverity("Major");
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(exc));
Hope this helps!

Jackson Mapper post-construct

I am using the Jackson ObjectMapper to deserialize some JSON into a Java class, which we'll call PlayerData. I would like to add a bit of logic to the PlayerData class to fix up some data after the fields have been loaded in. For example, some early JSON files used to use a "sex" flag instead of a "gender" falg, so if the sex flag is set but the gender flag is not set, I'd like to set the value of the gender field to be the value of the sex field.
Is there some sort of #PostConstruct or #AfterLoad annotation that I could affix to a method? Or perhaps an interface that I could implement? I didn't notice one in the documentation, but it seemed like an obvious feature.
Found this thru a link in the comments (credit: fedor.belov). This appears to allow you to run code post construct.
Adding a comment for people who end up here via
http://jira.codehaus.org/browse/JACKSON-645 or
http://jira.codehaus.org/browse/JACKSON-538 and are looking for a
method which is called after a deserializer completes. I was able to
achieve the desired effect by including an annotation and writing a
converter which uses the same class as input and output.
#JsonDeserialize(converter=MyClassSanitizer.class) // invoked after class is fully deserialized
public class MyClass {
public String field1;
}
import com.fasterxml.jackson.databind.util.StdConverter;
public class MyClassSanitizer extends StdConverter<MyClass,MyClass> {
#Override
public MyClass convert(MyClass var1) {
var1.field1 = munge(var1.field1);
return var1;
}
}
If you're not using the #JsonCreator, then Jackson will use the setter and getter methods to set the fields.
So if you define the following methods assuming that you have Sex and Gender enums:
#JsonProperty("sex")
public void setSex(final Sex sex) {
this.sex = sex;
if (gender == null) {
gender = (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
#JsonProperty("gender")
public void setGender(final Gender gender) {
this.gender = gender;
if (sex == null) {
sex = (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
}
it would work.
Update: You can find all of the annotations of Jackson library here.
Update2: Other solution:
class Example {
private final Sex sex;
private final Gender gender;
#JsonCreator
public Example(#JsonProperty("sex") final Sex sex) {
super();
this.sex = sex;
this.gender = getGenderBySex(sex)
}
#JsonFactory
public static Example createExample(#JsonProperty("gender") final Gender gender) {
return new Example(getSexByGender(gender));
}
private static Sex getSexByGender(final Gender) {
return (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
private static Gender getGenderBySex(final Sex) {
return (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
This is not supported out of the box, but you can easily create your #JsonPostDeserialize annotation for methods to be called after deserialization.
First, define the annotation:
/**
* Annotation for methods to be called directly after deserialization of the object.
*/
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonPostDeserialize {
}
Then, add the following registration and implementation code to your project:
public static void addPostDeserializeSupport(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDescription,
JsonDeserializer<?> originalDeserializer) {
return new CustomAnnotationsDeserializer(originalDeserializer, beanDescription);
}
});
objectMapper.registerModule(module);
}
/**
* Class implementing the functionality of the {#link JsonPostDeserialize} annotation.
*/
public class CustomAnnotationsDeserializer extends DelegatingDeserializer {
private final BeanDescription beanDescription;
public CustomAnnotationsDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
super(delegate);
this.beanDescription = beanDescription;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new CustomAnnotationsDeserializer(newDelegatee, beanDescription);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Object deserializedObject = super.deserialize(p, ctxt);
callPostDeserializeMethods(deserializedObject);
return deserializedObject;
}
private void callPostDeserializeMethods(Object deserializedObject) {
for (AnnotatedMethod method : beanDescription.getClassInfo().memberMethods()) {
if (method.hasAnnotation(JsonPostDeserialize.class)) {
try {
method.callOn(deserializedObject);
} catch (Exception e) {
throw new RuntimeException("Failed to call #JsonPostDeserialize annotated method in class "
+ beanDescription.getClassInfo().getName(), e);
}
}
}
}
}
Finally, modify your ObjectMapper instance with addPostDeserializeSupport, it will invoke all #JsonPostDeserialize annotated method of deserialized objects.
This is something that has actually been suggested couple of times earlier. So maybe filing an RFE would make sense; there are multiple ways in which this could work: obvious ones being ability to annotate type (#JsonPostProcess(Processor.class)) and ability to register post-processor through Module API (so that there's basically a callback when Jackson constructs deserializer, to let module specify post-processor to use if any). But perhaps there are even better ways to do this.

Categories