This question already has answers here:
Different names of JSON property during serialization and deserialization
(14 answers)
Closed 1 year ago.
I have the following requirement for JSON string conversion to Java Object.
class Person {
private String firstName;
private String lastName;
}
ObjectMapper MAPPER = new ObjectMapper();
String jsonString = "{\"FST_NME\":\"stack\",\"LST_NME\":\"OVERFLOW\"}";
Person person = MAPPER.readValue(jsonString, Person.class);
The above conversion returns null as the Person class attribute name doesn't match.
With #JsonProperty it converts correctly, but the final JSON result key is the same key as in jsonString.
{
"FST_NME" : "stack",
"LST_NME" : "overflow"
}
but I am looking for something like below.
{
"firstName" : "stack",
"lastName" : "overflow"
}
I tried renaming the key in jsonString and it works as expected.
But can we achieve the above result using any annotations or any other approach?
Thanks.
You just need to add #JsonProperty in both setter and getters.
In your case,
You are reading JSON string key FST_NME, so you need to add #JsonProperty('FST_NME') in the setter method for firstName and as you want to get the final JSON string with key firstName so you need to add #JsonProperty('firstName') in the getter method of firstName.
And same for lastName.
Following is the working code.
package com.ubaid.stackoverflow;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
#Slf4j
public class Saravanan {
#SneakyThrows
public static void main(String[] args) {
ObjectMapper MAPPER = new ObjectMapper();
String jsonString = "{\"FST_NME\":\"stack\",\"LST_NME\":\"OVERFLOW\"}";
Person person = MAPPER.readValue(jsonString, Person.class);
String finalJson = MAPPER.writeValueAsString(person);
log.debug("Final JSON: {}", finalJson);
}
}
class Person {
private String firstName;
private String lastName;
#JsonProperty("firstName")
public String getFirstName() {
return firstName;
}
#JsonProperty("FST_NME")
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#JsonProperty("lastName")
public String getLastName() {
return lastName;
}
#JsonProperty("LST_NME")
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
The output of above code is:
Final JSON: {"firstName":"stack","lastName":"OVERFLOW"}
Add below annotation on gets methods.
#JsonGetter("FST_NME")
public String getFirstName(){
return first Name;
}
Read data to a DTO class (Person DTO) with #JsonProperty
Class Person as you wish
Convert DTO to Person
class PersonDTO {
#JsonProperty(value = "FST_NME")
private String firstName;
#JsonProperty(value = "LST_NME")
private String lastName;
}
class Person {
private String firstName;
private String lastName;
}
ObjectMapper MAPPER = new ObjectMapper();
String jsonString = "{\"FST_NME\":\"stack\",\"LST_NME\":\"OVERFLOW\"}";
PersonDTO persondto = MAPPER.readValue(jsonString, PersonDTO.class);
Person person = new Person();
person.setFirstName(persondto.getFirstName());
person.setLastName(persondto.getLastName());
Related
I have a Json response as follows:
{
"data": {
"date": "7 Apr 2022",
"employee": [
{
"id": [
"1288563656"
],
"firstname": [
"Mohammed"
],
"lastname": [
"Ali"
]
}
]
}
}
I am trying to create a POJO called Employee and map it to the "employee" attribute in the JSON response.
This is what I did as an attempt:
Employee.java
public class Emoloyee {
private Integer[] id;
private String[] firstname;
private String[] lastname;
public Employee(Integer[] id, String[] firstname, String[] lastname){
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
}
public Employee(){
}
public Integet[] getId(){
return id;
}
public void setId(Integer[] id){
this.id = id;
}
public String[] getFirstname(){
return firstname;
}
public void setFirstname(String[] firstname){
this.firstname = firstname;
}
public String[] getLastname(){
return lastname;
}
public void setLastname(String[] lastname){
this.lastname = lastname;
}
}
Using Jackson:
ObjectMapper mapper = new ObjectMapper();
URL jsonUrl = new URL("[API_URL]");
final ObjectNode node = mapper.readValue(jsonUrl, ObjectNode.class);
Employee[] employees = mapper.treeToValue(node.get("data").get("employee"), Employee[].class);
When I execute the app, I get the following error:
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type 'long' from Array value (toke 'JsonToken.START_ARRAY')
As you may noticed, I am not interested of the date attribute, I only need to get the values of the employee and create an Employee object out of it.
The Employee POJO should look like this:
#JsonProperty("id")
private final List<String> ids;
#JsonProperty("firstname")
private final List<String> firstNames;
#JsonProperty("lastname")
private final List<String> lastNames;
#JsonCreator
public Employee(#JsonProperty(value = "id") List<String> ids, #JsonProperty(value = "firstname") List<String> firstNames, #JsonProperty(value = "lastname") List<String> lastNames) {
this.ids = ids;
this.firstNames = firstNames;
this.lastNames = lastNames;
}
//getters code
Then, you have an object Data:
#JsonProperty("date")
private final String date;
#JsonProperty("employee")
private final List<Employee> employees;
#JsonCreator
public Data(#JsonProperty(value = "date") String date, #JsonProperty(value = "employee") List<Employee> employees) {
this.date = date;
this.employees = employees;
}
//getters code
Finally, the whole Answer that you want to parse has this shape:
#JsonProperty("data")
private final Data data;
#JsonCreator
public Answer(#JsonProperty(value = "data") Data data) {
this.data = data;
}
//getter code
Once you have defined these 3 classes, then you will be able to do:
ObjectMapper objectMapper = new ObjectMapper();
Answer answer = objectMapper.readValue(yourStringAnswer, Answer.class);
Note: in your question, you are trying to parse an URL to an ObjectNode. I hardly doubt you would be able to do that.
I guess you want to perform the HTTP request to the URL, then getting the response stream and that's what you want to parse into Answer (not the URL itself).
Also, a few notes on the API response (in case you own it and so you can act on it):
All the lists would be more naturally declared with a plural name (e.g. employee should be employees)
The list of ids are numeric but are returned as strings. Also, why an employee would have a list of ids, and not a single id?
Why would an employee have a list of first names and last names? Shouldn't this be a simple string each (even if composed by more than one name)?
Use camel case (firstName, not firstname)
I don't see the point of putting everything into data, it may simply be the response containing date and employees
I have a JSON structure where the key and value are stored like this
[
{
"key": "firstName",
"value": "John"
},
{
"key": "lastName",
"value": "Smith"
}
]
I would like to deserialize the array so that each key is a property name, and the appropriate value assigned.
public class Customer {
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
I have thought of creating a custom deserializer to accept an array of customer attributes, and manually setting each property based on the attribute name. My concern is this approach could be become brittle with the addition of more properties, and is not maintainable in the long term.
Does anyone know of any Jackson annotations that I may have overlooked that would aid in this deserialization?
Jackson will be able to deserialise a POJO from JSON Object or Map. You have a list of mini-Maps where each mini-Map contains exactly one property. You need to:
Deserialise input payload to List<Map<String, Object>>
Transform it to single Map object
Convert to Customer instance
Simple example:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class JsonMiniMapsApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
// read as list of maps
List<Map<String, Object>> entries = mapper.readValue(jsonFile, new TypeReference<List<Map<String, Object>>>() {
});
// collect all values to single map
Map<Object, Object> map = entries.stream().collect(Collectors.toMap(e -> e.get("key"), e -> e.get("value")));
// convert to POJO
Customer customer = mapper.convertValue(map, Customer.class);
System.out.println(customer);
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
class Customer {
private String firstName;
private String lastName;
}
Above code prints:
Customer(firstName=John, lastName=Smith)
Just annotate the properties with #JsonProperty
public class Customer {
#JsonProperty("key")
private String firstName;
#JsonProperty("value")
private String lastName;
....
}
if you want to keep the names during serialization, you should annotate the setter and getter instead of the field.
Here is an example: Different names of JSON property during serialization and deserialization
This can be solved in a quite generic way with Jackson.
First, you will need a small Java class representing any
key/value pair (like for example {"key":"firstName","value":"John"})
from your JSON input. Let's call it Entry.
public class Entry {
private String key;
private Object value;
// getters and setters (omitted here for brevity)
}
Using the class above you can deserialize the JSON input
(as given in your question) to an array of Entry objects.
ObjectMpper objectMapper = new ObjectMapper();
File file = new File("example.json");
Entry[] entries = objectMapper.readValue(file, Entry[].class);
Then you can convert this Entry[] array to a Map<String, Object>,
and further to a Customer object
(using the Customer class as given in your question).
Map<String, Object> map = Arrays.stream(entries)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
Customer customer = objectMapper.convertValue(map, Customer.class);
While parsing CSV with with a custom column separator ';' and using :
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
I get the following error:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Author] from String value ('Email;FirstName;LastName'); no single-String constructor/factory method
here is my parsing code:
public static <T> List<T> loadObjectList(Class<T> type, String fileName) {
CsvSchema bootstrapSchema = CsvSchema.emptySchema();
bootstrapSchema.withColumnSeparator(';').withoutQuoteChar();
CsvMapper mapper = new CsvMapper();
File file = new File(fileName);
MappingIterator<T> readValues =
mapper.reader(type)
.with(bootstrapSchema)
.readValues(new InputStreamReader(new FileInputStream(file), "ISO-8859-1"));
return readValues.readAll();
}
and here is my model i want to map to CSV to:
#JsonPropertyOrder({ "Email", "FirstName", "LastName" })
public class Author {
String Email;
String FirstName;
String LastName;
public String getEmail() {
return Email;
}
public void setEmail(String Email) {
this.Email = Email;
}
public String getFirstName() {
return FirstName;
}
public void setFirstName(String firstName) {
FirstName = firstName;
}
public String getLastName() {
return LastName;
}
public void setLastName(String lastName) {
LastName = lastName;
}
public Author(String email, String firstName, String lastName) {
Email = email;
FirstName = firstName;
LastName = lastName;
}
}
As is often the case,
the solution to your problem is this: Pay attention to the exception message.
Here is the exception message that you included:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Author] from String value ('Email;FirstName;LastName'); no single-String constructor/factory method
Here is a 3 step solution to your question:
Step 1: Notice this in the exception message: no single-String constructor/factory method
Step 2: Take a look at the Author class that you included.
Notice that it does not include either a constructor or a factory method that accepts
exactly one String as a parameter.
Step 3: Add a constructor to the Author class that accepts a single String as the parameter.
Here is some code (this is not the final code, you will need to add error handling):
public Author(final String line)
{
final String[] lineContentArray = line.split(";");
// Note: consider using the standard java field naming convention.
email = line[0];
firstName = line[1];
lastName = line[2];
}
CsvSchema bootstrapSchema = CsvSchema.emptySchema();
bootstrapSchema.withColumnSeparator(';').withoutQuoteChar();
I'm not sure in this part - it's not a builder pattern.
Try
CsvSchema bootstrapSchema = CsvSchema.emptySchema().bootstrapSchema.withColumnSeparator(';').withoutQuoteChar();
instead.
Accepted answer indeed allows solving the OP issue according to the suggestion on the exception message.
However, the whole point in using CSVMapper is about not writing code for the split around separator and assignment to fields. That is what the lib is supposed to do.
As I encountered same issue, I digged up a bit and found the following. It is all about using typedSchemaFor where type is the target class.
Indeed bit disturbing to have to pas the type as to both mapper and schema.
ObjectReader objectReader() {
CsvMapper mapper = new CsvMapper();
CsvSchema schema = schema(mapper);
return mapper.readerFor(type).with(schema);
}
CsvSchema schema(CsvMapper mapper) {
return mapper.typedSchemaFor(type)
.withColumnSeparator(';')
.withoutHeader()
.withoutQuoteChar();
}
Thanks for the question about the mismatch of property name. However, I have made them consistent but same error throws.
I'm using Jackson to convert JSON array to Java object array.
The code is as simple as below, here is the code entry:
import java.io.File;
import com.fasterxml.jackson.databind.ObjectMapper;
import jackson.vo.User;
public class JsonConvertTest {
public static void main(String args[]){
try{
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("results.json");
User[] users= objectMapper.readValue(file, User[].class);
}catch(Exception e){
e.printStackTrace();
}
}
}
Here is the value object,
package jackson.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
#JsonProperty("firstName")
String firstName;
#JsonProperty("lastName")
String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Here is the JSON array:
{
"users":[
{ "firstName":"Tom", "lastName":"Jackson"},
{ "firstName":"Jenny", "lastName":"Mary"},
{ "firstName":"Red", "lastName":"Blue"},
{ "firstName":"Jason", "lastName":"John"},
{ "firstName":"May", "lastName":"Black"}
]
}
The output is:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `jackson.vo.User[]` out of START_OBJECT token
at [Source: (File); line: 1, column: 1]
Thanks for your help in advance.
Root level object in your JSON file is a JSON object, yet you are telling jackson to read the file as an array of users.
Try with the following contents:
[
{ "firstName":"Tom", "lastName":"Jackson"},
{ "firstName":"Jenny", "lastName":"Mary"},
{ "firstName":"Red", "lastName":"Blue"},
{ "firstName":"Jason", "lastName":"John"},
{ "firstName":"May", "lastName":"Black"}
]
I am fairly certain this is an issue of case sensitivity. Jackson by default looks for property "getters" so you have "getFirstName". But, your JSON file is "firstname".
Try again with the case sensitivity the same between your source json file and your class.
Note the spelling of lastname(your POJO) vs lastName(the Json). Either change your pojo to firstName and lastName to match those of your JSON or set annotation above each field like below:
public class User {
#JsonProperty("firstname");
String firstName;
#JsonProperty("lastname");
String lastName;
This is because of the property mismatch. You can try this
public class User {
#JsonProperty("firstname")
String firstName;
#JsonProperty("lastname")
String lastName;
}
#JsonProperty is used to indicate external property name, name used in data format (JSON or one of other supported data formats)
I currently using jsoup and sometimes dom4j for parsing string of xml.
Here's an example on how I do it using jsoup.
Document doc = Jsoup.parse(xml);
Elements root = doc.select("person");
for(Elements elem : elements){
Person person = new Person();
person.setFirstname(elem.select("firstName").text());
person.setLastname(elem.select("lastName").text());
person.setAddress(elem.select("address").text());
//other setters here
}
Everytime I have to parse xml I have to get all elements and set to setters of POJO. Now I want to create a Generics where I only have to do is to passed a string of xml and a class of POJO then it will set all the values of fields based on all the elements of xml. How can I do it? Any ideas?
Thanks in advance.
JAXB is the way to go.
Note:
It is included in JAVA 1.6 and later versions
Add XML tags to your POJO (XmlRootElement is enough for simple objects, XmlElement can also be added to variables)
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "Person")
public class Person {
private String firstName;
private String lastName;
private String address;
public final String getFirstName() {
return firstName;
}
public final void setFirstName(String firstName) {
this.firstName = firstName;
}
public final String getLastName() {
return lastName;
}
public final void setLastName(String lastName) {
this.lastName = lastName;
}
public final String getAddress() {
return address;
}
public final void setAddress(String address) {
this.address = address;
}
#Override
public String toString() {
return "FirstName: " + firstName + " LastName: " + lastName + " Address: " + address;
}
}
Use Unmarshaller to create the POJO from the xml file.
File file = new File("<Path to Xml file>");
JAXBContext context = JAXBContext.newInstance(Person.class);
Unmarshaller unmarsheller = context.createUnmarshaller();
Person person = (Person) unmarsheller.unmarshal(file);
System.out.println(person);
You can use Marshaller to create the XML from the POJO also.
There are more examples available here to create complex objects, add lists, arrays.
Note: It is not available in Android Platform, If you want to use it on android you can use SimpleXML with almost same implementation