We're using Jackson JSON mapper in our code to de-serialize some configuration objects. We'd like for Jackson to fail on de-serialization when specific fields are missing or empty
The only feature in Jackson to support this behavior is for primitives :
final DeserializationConfig.Feature failOnPremitives = DeserializationConfig.Feature.FAIL_ON_NULL_FOR_PRIMITIVES;
The thing is the fields in question are mainly strings
Any help is highly appreciated
There is an option called: FAIL_ON_NULL_FOR_CREATOR_PARAMETERS.
So I assume it would be accessible at: DeserializationConfig.Feature.FAIL_ON_NULL_FOR_CREATOR_PARAMETERS;
Or in yml:
jackson:
serialization:
indent-output: false
deserialization:
fail-on-unknown-properties: true
fail-on-missing-creator-properties: true
fail-on-null-creator-properties: true
This works on all types, strings, ints, doubles etc..
Have you considered Bean Validation?
While Jackson is focused in JSON parsing, Bean Validation is all about declaring and performing validation on your beans.
You could use #NotNull or #NotBlank from Hibernate Validator, the Bean Validation reference implementation.
Alternatively you could use JSON Schema.
Object specific custom Deserializer would have to be created.
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
class JacksonDeserializerTest {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomPersonDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Person.class, new CustomPersonDeserializer());
mapper.registerModule(module);
String jsonString = "{ \"id\": 1, \"name\": \"User 1 \"}";
Person user = mapper.readValue(jsonString, Person.class);
System.out.println("User: " + user.toString());
jsonString = "{ \"id\": 1}";
user = mapper.readValue(jsonString, Person.class);
}
static class CustomPersonDeserializer extends StdDeserializer<Person> {
private static final long serialVersionUID = -4100181951833318756L;
public CustomPersonDeserializer() {
this(null);
}
public CustomPersonDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Person deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException, JsonProcessingException {
Person person = new Person();
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
JsonNode idNode = node.get("id");
int id = idNode.asInt();
person.setId(id);
JsonNode nameNode = node.get("name");
if(nameNode == null){
throw new IOException("name must be provided");
}
String name = nameNode.asText();
if (name.trim().length() < 1){
throw new IOException("name can not be empty");
}
person.setName(name);
return person;
}
}
static class Person {
private int id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
}
Alternate way:
This works on the objects as well
ObjectMapper mapper= new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Related
I am trying to unmarshal a json response which comes from a server. But I would like to know which is the best way, approach to use when the json response changes.
For example, if I have a json response like this:
{
"name": "John",
"last_name": "John Last name",
"date_of_birth": "01.01.1990"
}
With jackson, I could deserialize the json object into a Person.class like this:
#NoARgsConstructor
#Setter
public class Person(){
private String name;
private String lastName;
private String dateOfBirth;
}
But what if the json struct changes, and now the attributes of the person comes inside a person object.
{
"person": {
"name": "John",
"last_name": "John Last name",
"date_of_birth": "01.01.1990"
}
}
What would be the best way to avoid this things or to avoid this problems? Is there any possible solution or approach to implement in java spring?
How about searching in the JSON yourself?
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class Foo {
public static void main(String args[]) throws Exception {
String jsonString = "{\"foo\":\"bar\"}";
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.readValue(jsonString, ObjectNode.class);
if(node.has("foo")) {
System.out.println("foo: " + node.get("foo"));
}
}
}
To make it completely dynamic, I have used reflection api and json-simple-1.1 jar and jackson
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
#JsonProperty("name")
private String name;
#JsonProperty("last_name")
private String lastName;
#JsonProperty("date_of_birth")
private String dateOfBirth;
}
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Demo {
public static void main(String a[]) {
try {
String json1 = "{\r\n" + " \"person\": {\r\n" + " \"name\": \"John\",\r\n"
+ " \"last_name\": \"John Last name\",\r\n" + " \"date_of_birth\": \"01.01.1990\"\r\n"
+ " } \r\n" + " }";
String json2 = "{\r\n" + " \"name\": \"John\",\r\n" + " \"last_name\": \"John Last name\",\r\n"
+ " \"date_of_birth\": \"01.01.1990\"\r\n" + "} ";
extractedPersonObject(json1);
System.out.println("*****************************");
extractedPersonObject(json2);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void extractedPersonObject(String json2) throws ParseException {
Person person = new Person();
Object obj = new JSONParser().parse(json2);
JSONObject jo = (JSONObject) obj;
Map finalMap;
Field[] fields = Person.class.getDeclaredFields();
if (jo.get(fields[0].getName()) == null) {
finalMap = ((Map) jo.get("person"));
} else
finalMap = (Map) jo;
for (Field field : fields) {
JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
invokeSetter(person, field.getName(), finalMap.get(jsonProperty.value()));
}
System.out.println(person.getDateOfBirth());
System.out.println(person.getLastName());
System.out.println(person.getName());
}
public static void invokeSetter(Object obj, String propertyName, Object variableValue) {
PropertyDescriptor pd;
try {
pd = new PropertyDescriptor(propertyName, obj.getClass());
Method setter = pd.getWriteMethod();
try {
setter.invoke(obj, variableValue);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
}
A generic way to deserialize such wrapped is to write a deserializer of your own, like:
#SuppressWarnings("serial")
public class UnwrappingDeserializer<T> extends StdDeserializer<T> {
#Setter
private String fieldName;
private ObjectMapper innerMapper = new ObjectMapper();
public UnwrappingDeserializer(Class<T> vc) {
super(vc);
fieldName = handledType().getSimpleName().toLowerCase();
}
#SuppressWarnings("unchecked")
#Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode rootT = p.readValueAsTree();
// Check if there is a node with specified field name.
// There is also a setter for it if the field name is not
// directly resolvable
JsonNode valueT = rootT.get(fieldName);
if (valueT == null) {
// If no such node it is the root tha has the value
valueT = rootT;
}
return innerMapper.convertValue(valueT, (Class<T>)handledType());
}
}
Assuming you have Person as:
#Getter #Setter
// Below is because of your JSON key format
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Person {
private String name;
private String lastName;
private String dateOfBirth;
}
you can just add the deserializer to your ObjectMapper like:
ObjectMapper om = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(Person.class, new UnwrappingDeserializer<Person>(Person.class));
om.registerModule(sm);
I have access to a RESTful API which returns JSON Strings, such as the following:
{
"Container1": {
"active": true
},
"Container2": {
"active": false
},
}
The problem is that the RESTful API is a bit maldesigned. The field name contains the data already. With the Jackson library it is not possible to deserialize the field name to a property name of the corresponding Java bean class. I assume, this isn't intended by the JSON specification neither. The above JSON string needs to be deserialized to an instance of the following class:
public class Container {
private Boolean active;
private String name;
}
I end up with UnrecognizedPropertyException for the field Container1.
I thought to configure to ignore unknown properties and to provide a JsonDeserializer for that property like this:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Container {
private Boolean active;
private String name;
#JsonDeserialize(using = FieldNameToPropertyDeserializer.class)
public void setName(String name) {
this.name = name;
}
}
and the FieldNameToPropertyDeserializer:
public class FieldNameToPropertyDeserializer extends StdDeserializer<String> {
public FieldNameToPropertyDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
return parser.getCurrentName();
}
}
The invocation of the deserialization is achieved as follows:
String jsonString = response.readEntity(String.class);
ObjectMapper objectMapper = new ObjectMapper();
ObjectReader readerFor = objectMapper.readerFor(Container.class);
MappingIterator<Container> mappingIterator = readerFor.readValues(jsonString);
while (mappingIterator.hasNext()) {
Container container = (Container) mappingIterator.next();
containers.add(container);
}
But I only receive empty objects (properties set to null) because the parsing of the properties is skipped since I set #JsonIgnoreProperties(ignoreUnknown = true).
Is this possible at all? Or should I implement something like a post-processing afterwards?
How about this. Create a class ContainerActive like this
public class ContainerActive {
private boolean active;
// constructors, setters, getters
}
And you could just do
Map<String, ContainerActive> map = mapper.readValue(jsonString, new TypeReference<Map<String, ContainerActive>>() {});
With this you will have "Container1", "Container2" as the keys and ContainerActive Object as values which has active field.
Just a quick solution, if the object is such that, that all of it object is a container object you can receive the JSON inside and JSONObject you may use below code
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestSO {
public static void main(String[] args) throws JsonParseException, JsonMappingException, JSONException, IOException {
String jsonString = "{\r\n" +
" \"Container1\": {\r\n" +
" \"active\": true\r\n" +
" },\r\n" +
" \"Container2\": {\r\n" +
" \"active\": false\r\n" +
" },\r\n" +
"}";
JSONObject jsonObject = new JSONObject(jsonString);
ObjectMapper mapper = new ObjectMapper();
for (String key : jsonObject.keySet()) {
Container container = mapper.readValue(jsonObject.get(key).toString(), Container.class);
System.out.println(container);
}
}
static class Container{
private String name;
private Boolean active;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
#Override
public String toString() {
return "Container [name=" + name + ", active=" + active + "]";
}
}
}
I'm mapping object (code to which I have no access) into json using Jackson Object Mapper and MixIn class. So, for example i have this object:
public class Person {
private String name;
private String surname;
public Person (String name, String surname) {
this.name = name;
this.surname = surname;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}
and MixIn class like this:
abstract class MixIn {
#JsonIgnore
abstract String getSurname(); // we don't need this in json output!
}
This working well. But what if i need to change output of "getName()" getter to return like return person.name + " " + person.surname.
Is this even possible to do by specific Object Mapper configuration? (when i have no access to Person class code)
You could write a custom serializer:
class PersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person person, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeStartObject();
generator.writeStringField("name", person.getName() + " " + person.getSurname());
generator.writeEndObject();
}
}
Then, there are multiple ways to register it, for example using a module:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Person.class, new PersonSerializer());
mapper.registerModule(module);
System.out.println(mapper.writeValueAsString(new Person("user", "1827257")));
You may want to serialize the object differently in different cases, so using mixins is preferred:
objectMapper.getSerializationConfig().addMixInAnnotations(Person.class, MixIn.class);
String json = objectMapper.writeValueAsString(new Person("user", "1827257"));
We have a structure that represents configuration of some sort. We have had a typo in the word periodicity, it was wrongly spelled with 'o' as period*o*city. Below example source is the corrected one. However, I need to be able to read the old configuration files to maintain backwards compatibility.
Can I make JSON Jackson recognize the misspelled field/property on deserialization but ignore it on serialization?
We are using version 2.6.6 of JSON Jackson.
package foo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
#JsonInclude(Include.NON_EMPTY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Rule {
private LogPeriodicity periodicityLevel;
private Integer periodicity;
// ctors and some other methods omitted for brevity
public LogPeriodicity getPeriodicityLevel() {
return periodicityLevel;
}
public void setPeriodicityLevel(LogPeriodicity periodicityLevel) {
this.periodicityLevel = periodicityLevel;
}
public Integer getPeriodicity() {
return periodicity;
}
public void setPeriodicity(Integer periodicity) {
this.periodicity = periodicity;
}
}
If i got your question right you want something like this?
MyClass obj = mapper.readValue("{ \"name\" : \"value\"}", MyClass.class);
String serialized = mapper.writeValueAsString(obj);
MyClass obj2 = mapper.readValue("{ \"name2\" : \"value\"}", MyClass.class);
String serialized2 = mapper.writeValueAsString(obj2);
if( Objects.equals(serialized2, serialized))
System.out.println("Success " + serialized + " == " + serialized2 );
if you don't want extra field in POJO you can just add setter like this:
public static class MyClass {
#JsonProperty
private String name = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonSetter
public void setName2(String name2) {
setName(name2);
}
}
You can probably also register legacy Mixin instead of #JsonSetter
public abstract class LegacyMyClassMixIn{
#JsonProperty("name")
private String name;
#JsonGetter("name")
public abstract String getName();
#JsonSetter("name2")
public abstract void setName(String name) ;
}
And use it like this:
SimpleModule module = new SimpleModule();
module.setMixInAnnotation(MyClass.class, LegacyMyClassMixIn.class);
mapper2.registerModule(module);
Btw in Gson it can be done with just 1 line #SerializedName(value="name", alternate={"name2"}) public String name = null;
I haven't worked with JSON data before, thus the question.
I've the following JSON object in a file.
{
"courses": [
{ "id":998", "name":"Java Data Structures", "teacherId":"375" },
{ "id":"999", "name":"Java Generics", "teacherId":"376" }
],
"teachers": [
{ "id":"375", "firstName":"Amiyo", "lastName":"Bagchi"},
{ "id":"376", "firstName":"Dennis", "lastName":"Ritchie"}
]
}
Here are my model Objects.
public class Course {
private int _id;
private String _name;
private Teacher _teacher;
}
public class Teacher {
private int _id;
private String _firstName;
private String _lastName;
}
My task is to read the JSON Objects and return a list of Model objects.
I've imported the simple.JSON family of jar and here's my code that reads the file.
FileReader reader = new FileReader(path);
JSONParser parser = new JSONParser();
Object obj = parser.parse(reader);
JSONObject jsonObject = (JSONObject) obj;
My question is,
How do I parse the JSON document into my Model objects?
If the input file is JSON but of a different format how do I throw exception/handle the anomaly?
Any help appreciated.
UPDATE I suggest you use JSON parser to parse the data:
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
class Course {
public int _id;
public String _name;
public Teacher _teacher;
private Course(int id, String name, Teacher teacher){
this._id = id;
this._name = name;
this._teacher = teacher;
}
public Course() {
}
}
class Teacher {
public int _id;
public String _firstName;
public String _lastName;
private Teacher(int id, String fname, String lname){
this._id = id;
this._firstName = fname;
this._lastName = lname;
}
public Teacher(){
}
}
public class jsontest {
public static void main(String[] args) throws JSONException, IOException {
// String JSON_DATA = "{\n"+
// " \"courses\": [\n"+
// " { \"id\":\"998\", \"name\":\"Java Data Structures\", \"teacherId\":\"375\" },\n"+
// " { \"id\":\"999\", \"name\":\"Java Generics\", \"teacherId\":\"376\" }\n"+
// "\n"+
// " ],\n"+
// " \"teachers\": [\n"+
// " { \"id\":\"375\", \"firstName\":\"Amiyo\", \"lastName\":\"Bagchi\"},\n"+
// " { \"id\":\"376\", \"firstName\":\"Dennis\", \"lastName\":\"Ritchie\"} \n"+
// " ]\n"+
// "}\n"+
// "";
// read json file into string
String JSON_DATA = new String(Files.readAllBytes(Paths.get("path_to_json_file")), StandardCharsets.UTF_8);
// using a JSON parser
JSONObject obj = new JSONObject(JSON_DATA);
// parse "teachers" first
List<Teacher> listCourses = new ArrayList<Teacher>();
List<JSONObject> listObjs = parseJsonData(obj,"teachers");
for (JSONObject c: listObjs) {
Teacher teacher = new Teacher();
teacher._id = c.getInt("id");
teacher._firstName = c.getString("firstName");
teacher._lastName = c.getString("lastName");
listCourses.add(teacher);
}
// parse "courses" next
List<Course> resultCourses = new ArrayList<Course>();
List<JSONObject> listObjs2 = parseJsonData(obj, "courses");
for (JSONObject c: listObjs2) {
Course course = new Course();
course._id = c.getInt("id");
course._name = c.getString("name");
int teacherId = c.getInt("teacherId");
HashMap<String, Teacher> map = new HashMap<String, Teacher>();
for (Teacher t: listCourses){
map.put(Integer.toString(t._id), t);
}
course._teacher = map.get(Integer.toString(teacherId));
resultCourses.add(course);
}
}
public static List<JSONObject> parseJsonData(JSONObject obj, String pattern)throws JSONException {
List<JSONObject> listObjs = new ArrayList<JSONObject>();
JSONArray geodata = obj.getJSONArray (pattern);
for (int i = 0; i < geodata.length(); ++i) {
final JSONObject site = geodata.getJSONObject(i);
listObjs.add(site);
}
return listObjs;
}
}
Output:
BTW: The json data in the example has one value whose double quotes are not in pairs. To proceed, it must be fixed.
You should try using Jackson as the JSON parsing library instead. There is a lot more support and features that come with it.
In your case, a couple of annotations to map the JSON properties to the Java fields should be sufficient.
https://github.com/FasterXML/jackson-annotations
https://github.com/FasterXML/jackson-databind
UPDATE: Some code, to show just much better this can be done with Jackson.
public class Course {
#JsonProperty("id")
private int _id;
#JsonProperty("name")
private String _name;
#JsonProperty("teacher")
private Teacher _teacher;
// ...public getters and setters
}
public class Teacher {
#JsonProperty("id")
private int _id;
#JsonProperty("firstName")
private String _firstName;
#JsonProperty("lastName")
private String _lastName;
// ...public getters and setters
}
// Container class to conform to JSON structure
public class CoursesDto {
private List<Teacher> teachers;
private List<Course> courses;
}
// In your parser place
ObjectMapper mapper = new ObjectMapper();
FileReader reader = new FileReader(path);
CoursesDto dto = mapper.readValue(reader, CoursesDto.class);
The #JsonProperty annotations tell Jackson what JSON key should be used to deserialize. They are not necessary if the property names match the JSON keys. That means that if you remove the leading underscore from your property names, this would work without annotations. Also, Jackson will default to using public fields and getter/setter methods. This means that you can keep your fields prefixed by _ as long as the getter/setter don't have it (setFirstName(String firstName)).