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);
Related
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 + "]";
}
}
}
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);
I have a form that should return list of customers.
This form should behave differently in two case:
User starts the research using only "surname"
User starts the research using surname AND name
In the first case the json response has less fields than the response in the second case so I have to ignore all these fields.
I've tried using #JsonInclude(JsonInclude.Include.NON_ABSENT), #JsonInclude(JsonInclude.Include.NON_EMPTY) and #JsonInclude(JsonInclude.Include.NON_NULL) but with each and everyone of these the error returned is always the same:
java.lang.Exception: Could not write content: (was java.lang.NullPointerException) (through reference chain: it.gruppoitas.itasacquire.pojo.Cliente["DATA_NASCITA"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: it.gruppoitas.itasacquire.pojo.Cliente["DATA_NASCITA"])
This is the pojo Cliente:
package it.gruppoitas.itasacquire.pojo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
#JsonInclude(JsonInclude.Include.NON_ABSENT)
public class Cliente {
#JsonProperty("TIPO_PERSONA")
private String tipoPersona;
#JsonProperty("PRO_CLIE")
private String proClie;
#JsonProperty("CODICE_FISCALE")
private String codiceFiscale;
#JsonProperty("DATA_NASCITA")
private String dataNascita;
#JsonProperty("SESSO")
private String sesso;
#JsonProperty("NOME")
private String nome;
#JsonProperty("COGNOME")
private String cognome;
public String getTipoPersona() {
return tipoPersona;
}
public void setTipoPersona(String tipoPersona) {
this.tipoPersona = tipoPersona;
}
public String getProClie() {
return proClie;
}
public void setProClie(String proClie) {
this.proClie = proClie;
}
public String getCodiceFiscale() {
return codiceFiscale;
}
public void setCodiceFiscale(String codiceFiscale) {
this.codiceFiscale = codiceFiscale;
}
public String getDataNascita() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
Date data = null;
try {
data = sdf.parse(dataNascita);
dataNascita = new SimpleDateFormat("dd/MM/yyyy").format(data);
} catch (ParseException e) {
System.err.println(e);
}
return dataNascita;
}
public void setDataNascita(String dataNascita) {
this.dataNascita = dataNascita;
}
public String getSesso() {
return sesso;
}
public void setSesso(String sesso) {
this.sesso = sesso;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getCognome() {
return cognome;
}
public void setCognome(String cognome) {
this.cognome = cognome;
}
#Override
public String toString() {
return "Cliente [tipoPersona=" + tipoPersona + ", proClie=" + proClie + ", codiceFiscale=" + codiceFiscale + ", dataNascita="
+ dataNascita + ", sesso=" + sesso + ", nome=" + nome + ", cognome=" + cognome + "]";
}}
Any idea?
EDIT: this is an example of the json response structure in case 1
{
"TIPO_PERSONA" : "G",
"PRO_CLIE" : "123456789",
"CODICE_FISCALE" : "123456789",
"PARTITA_IVA" : "123456789",
"SESSO" : "S",
"COGNOME" : "CUSTOMER SRL"
}
And this is an example of the json response in case 2:
{
"TIPO_PERSONA" : "F",
"PRO_CLIE" : "123456789",
"CODICE_FISCALE" : "123456789",
"DATA_NASCITA" : "1969-09-07 00:00:00.0",
"SESSO" : "F",
"NOME" : "Foo",
"COGNOME" : "Fie"
}
As you can see there are less fields in case 1 and STS goes in full-panic mode...
You need to configure your object mapper not to fail on empty beans.
Here is a sample code since you didn't provide the creation of the ObjectMapper code yourself:
private ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
jacksonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
You can also use:
jacksonMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,false);
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)).
I need to convert the following java class to json string with different property name :
public class Company {
private String companyYCode;
private String companyName;
getxx and setxx
}
I need this as a json string
{"y-code":"CICPK1214131231","company_name":"Some company" }
Yes, i think the simple way for you is using Jackson library for custom the json properties.
If you use maven, you can add dependency into your pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
And in your model class, just use the annotation JsonProperty to config the value you want:
public class Company {
#JsonProperty ("y-code")
private String companyYCode;
#JsonProperty ("company_name")
private String companyName;
}
With that, when you parse your object Company to Json, your json string will has properties which your want:
{"y-code":"CICPK1214131231","company_name":"Some company" }
You can write a method
public String toJson() {
JSONObjectBuilder json = new JSONObjectBuilder();
/*
add 'whatever you like - content' here
*/
return json.toString();
}
Use Jackson library...
public class Company {
#JsonProperty ("y-code")
private String companyYCode;
#JsonProperty ("company_name")
private String companyName;
getxx and setxx
}
main class...
import java.io.File;
import java.io.IOException;
import org.elasticsearch.common.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ObjToJson {
public static void main(String[] args) {
Company user = new Company();
ObjectMapper mapper = new ObjectMapper();
try {
mapper.writeValue(new File("c:\\user.json"), user);
System.out.println(mapper.writeValueAsString(user));
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
POJO :
public class Company {
private String companyYCode="CICPK1214131231";
private String companyName="Some company";
#Override
public String toString() {
return "User [companyYCode=" + companyYCode + ", companyName=" + companyName + "]";
}
public String getCompanyYCode() {
return companyYCode;
}
public void setCompanyYCode(String companyYCode) {
this.companyYCode = companyYCode;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
}