I have to use the exchange() method because I pass HttpHeaders there.
ResponseEntity<WeatherResponse> response Entity = restTemplate.exchange(
weather UrlRequest, Http Method.GET, new HttpEntity<>(headers), WeatherResponse.class);
JSON:
{
"geoloc": {
"city": {
"id": 213,
"name": "Boston"
},
"country": {
"id": 213,
"name": "USA"
},
"temp": {
"value": 19.4
}
}
Object to deserialization:
class WeahterResponse{
String country;
String city;
float temp;
}
How to influence deserialization in this case. There are two objects in JSON, and I need one?
class WeahterResponse{
GeoLocation geoloc;
Map<String,String> temp;
}
class GeoLocation {
Map<String,Map<String,Object> geoData;
}
It will deserialize your data to WeatherResponse.
Now if you want to get city data or country Data you can get that as follows.
suppose json is deserialized into weatherResponse.
Map<String,Map<String,Object> geoData = weatherResponse.getGeoLoc();
if(!CollectionUtils.isEmpty(geoData)){
if(geoData.containsKey("city")){
Map<String,Object> cityData = geoData.get("city");
System.out.println(cityData.get("id");
System.out.println(cityData.get("name");
}
//same for other keys of geoLoc
//to get Temp value
Map<String,String> temp = weatherResponse.getTemp();
System.out.println(temp.get("value");
Thanks for João Dias.
I made custom deserializer. https://www.baeldung.com/jackson-deserialization
In my case:
public class WeatherDeserializer extends JsonDeserializer<WeatherResponse> {
#Override
public WeatherResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode nodeTree = jsonParser.getCodec().readTree(jsonParser);
JsonNode geoObjectNode = nodeTree.get("geo_object");
JsonNode factNode = nodeTree.get("fact");
String country = geoObjectNode.get("country").get("name").textValue();
String province = geoObjectNode.get("province").get("name").textValue();
String locality = geoObjectNode.get("locality").get("name").textValue();
GeoObject geoObject = new GeoObject(country, province, locality);
Short temp = factNode.get("temp").shortValue();
Long obsTime = factNode.get("uptime").longValue();
return new WeatherResponse(geoObject,temp,obsTime);
}
}
#AllArgsConstructor
#Getter
#JsonDeserialize(using = WeatherDeserializer.class)
public class WeatherResponse {
private GeoObject geoObject;
private Short temp;
private Long uptime;
}
Related
Can somebody help me, how I can deserialize the following JSON, which I can not change?
I am using Jackson for serialization.
{
"columns": [
{
"header": "Heading1",
},
{
"header": "Heading2",
}
],
"rows": [
"id": 1,
"Heading1": {
"value": "Value1"
},
"Heading2": {
"value": "Value2"
}
]
}
Columns can have unknown number of headers and their value eg. "Header1" is used in the rows array.
So far I have the following structure:
public class QueryResult {
private ColumnConfig[] columns;
private QueryResultRow[] rows;
}
public class ColumnConfig {
private String header;
}
public class QueryResultRow {
private int id;
private Map<String, CellValue> values;
}
public class CellValue{
private String value;
}
The problem is that the Map is empty when I deserialize into QueryResult;
I read about TypeReference but I do not know how I can specify a TypeReference<HashMap<String,CellValue>> for the property values in QueryResultRow.
Edit:
My ObjectMapper code is the following:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String queryResultString = loadQuery(queryPath);
QueryResult result = mapper.readValue(queryResultString, QueryResult.class);
The content of queryResultString is the JSON above.
First problem is your JSON is invalid. I assume it should be something like this,
{
"columns": [
{
"header": "Heading1"
},
{
"header": "Heading2"
}
],
"rows": [
{
"id": 1,
"Heading1": {
"value": "Value1"
},
"Heading2": {
"value": "Value2"
}
}
]
}
Then answer is quite straightforward. You need to change your QueryResultRow as follows,
class QueryResultRow {
private int id;
private Map<String, CellValue> values = new HashMap<>();
#JsonAnySetter
public void addValues(String k, CellValue v) {
values.put(k, v);
}
}
Then I think you should good to go.
Here is a complete working example,
public class Main {
public static void main(String[] args) throws IOException {
String s = "{\"columns\":[{\"header\":\"Heading1\"},{\"header\":\"Heading2\"}],\"rows\":[{\"id\":1,\"Heading1\":{\"value\":\"Value1\"},\"Heading2\":{\"value\":\"Value2\"}}]}";
ObjectMapper om = new ObjectMapper();
QueryResult queryResult = om.readValue(s, QueryResult.class);
System.out.println(queryResult);
}
}
#Getter
#Setter
#ToString
class QueryResult {
private ColumnConfig[] columns;
private QueryResultRow[] rows;
}
#Getter
#Setter
#ToString
class ColumnConfig {
private String header;
}
#Getter
#Setter
#ToString
class QueryResultRow {
private int id;
private Map<String, CellValue> values = new HashMap<>();
#JsonAnySetter
public void addValues(String k, CellValue v) {
values.put(k, v);
}
}
#Getter
#Setter
#ToString
class CellValue{
private String value;
}
I am trying to map my incoming json payload to an arraylist of my model class.
I have a solution but its unintuitive.
I try to do this but get compilation errors-
ObjectMapper mapper = new ObjectMapper();
ArrayList<ModelClass> = mapper.readValue(items, RoleAttribute.class);
FYI I am trying to save this data in a Mongo collection.
Controller-
#PostMapping(value="/resource", consumes="application/json")
public Iterable<ModeClass> createResources(#RequestBody JSONObject requestBody ) throws JsonParseException, JsonMappingException, IOException {
System.out.println(requestBody.getClass());
return serviceImpl.saveResources(requestBody);
}
Model class-
#Data
#AllArgsConstructor
#Document(collection="collection-name")
public
class ModelClass{
#Field
private String ID;
#Field
private String description;
}
The payload is coming in the following format-
{
"data": [
{
"ID": "1",
"description": "desc1"
},
{
"ID": "2",
"description": "desc2"
},
{
"ID": "3",
"description": "desc3"
},
{
"ID": "4",
"description": "desc4"
}
....
]
}
I know I should be using jackson but I can't seem to figure this out. Do I need to change my POJO? Do I need to create custom Jackson config?
You can do it with json annotation. I also notice that your values are represented as data in json so that also needs to be taken care of. Look at below code. That will solve your problem.
import com.fasterxml.jackson.annotation.JsonProperty;
#Data
#AllArgsConstructor
#Document(collection="collection-name")
public class ModelClass{
#Field
#JsonProperty("ID")
private String classID;
#Field
#JsonProperty("description")
private String classDescription;
public String getClassID() {
return classID;
}
public void setClassID(String classID) {
this.classID = classID;
}
public String getClassDescription() {
return classDescription;
}
public void setClassDescription(String classDescription) {
this.classDescription = classDescription;
}
}
And wrapper Data class as below
class Data {
ModelClass[] data;
public ModelClass[] getData() {
return data;
}
public void setData(ModelClass[] data) {
this.data = data;
}
}
And json conversion code as below
ObjectMapper mapper = new ObjectMapper();
// json is your incoming json as a string. You can put inputstream also
Data values = mapper.readValue(json, Data.class);
System.out.println(values.getData().length);
System.out.println(values.getData()[0].getClassID());
You would need a container class for the data field, something like:
#Data
#Document(collection="collection-name")
public class DataClass{
private List<ModelClass> data;
}
Doing it via Jackson should be automatic this way, in controller:
public Iterable<ModeClass> createResources(#RequestBody DataClass requestBody ) {
I've got an JSON string from my API, looks like this:
[
{
"id": "abc",
"data": {
"Name": "Peter",
"Date": "2017/12/01"
}
},
{
"id": "def",
"data": {
"Name": "Tina",
"Date": "2017/12/20"
}
},
{
"id": "ghi",
"data": {
"Name": "Amy",
"Date": "2017/12/16"
}
}
]
Then, I use (java):
Gson gson = new Gson();
Type resultType = new TypeToken<List<Map<String, Object>>>() {
}.getType();
List<Map<String, Object>> result = gson.fromJson(info, resultType);
if I call result.get(0).toString());
then it returned:
{id=abc, data={Name=Peter, Date=2017/12/01}}
if I call result.get(0).get("id").toString();
then it returned
abc
Now I want to get the data of "data", when I call result.get(0).get("data").toString();
then it returned
{Name=Peter, Date=2017/12/01}
Finally I want to get the "Name" info, but when I tried to convert this string to Map, it cause some problem, the code is like this:
Type type = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> myMap = gson.fromJson(str, type);
This doesn't work. I found that maybe the string is not a general type of JSON, it is like "Name=Peter, Date=2017/12/01", but it needs "Name": "Peter", "Date": "2017/12/01" , right? Is that the problem? How can I get the data of Name? Can anyone help me?
Updated:
I found that if "Name" = "", then I couldn't get it as string type, I cannot use "data.get("Name");". But I still need it. Anyone can fix it? Thanks.
You can directly convert the response into the POJO/Model class. Check this and this
You don't need manual parsing, if you are using Gson. See how-
List<Response> responseList = new Gson().fromJson(yourJson, new TypeToken<List<Response>>() {
}.getType());
Data data = responseList.get(0).getData();
String id = responseList.get(0).getId();
String date = data.getDate();
String name = data.getName();
Isn't this magic? No manual parsing at all.
Response.java class
public class Response {
private Data data;
private String id;
public void setData(Data data) {
this.data = data;
}
public Data getData() {
return data;
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
Data.java class
public class Data {
private String date;
private String name;
public void setDate(String date) {
this.date = date;
}
public String getDate() {
return date;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
How to generate Pojo classes? So here is several websites jsonschema2pojo. Also many Android Studio plugins available, I use RoboPOJOGenerator.
First of all, your JSON is malformed, it shouldn't have a comma after date.
and to answer your question, don't use map at all.
If you really want to do it without creating a model and additional classes, do it this way:
Gson gson = new Gson();
Type resultType = new TypeToken<List<JsonObject>>() {}.getType();
List<JsonObject> result = gson.fromJson(info, resultType);
System.out.println(result.get(0).get("data").toString());
JsonObject data = result.get(0).get("data").getAsJsonObject();
System.out.println(data.get("Name"));
I've the following JSON from some upstream api
{
"Id": "",
"Name": "",
"Age": ""
}
And I need to map this above json to a downstream request paylaod (POJO) .
public class Employee
{
#JsonProperty("Id")
private Integer Id;
private User user;
}
public class User {
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private String age;
}
Right now I'm doing something like
Employee employee = new ObjectMapper().treeToValue(JsonNode node,Employee.class);
But this is giving null in User Object.
The challenge here is , that the json we are getting from upstream can't be changed . So , is there is any way to map the fields into the nested User object , without changing the structure of json received from upstream.
One Solution is : map the fields separately into User object and then set it into the Employee object . But that's not an efficient solution , because for null validations we would need to do validations separately for User and Employee objects. If the nesting is complex then , validation will be hell of replicated code .
Your JSON does not comply with your Employee class.
Because name and age is at the same level as id, but you want to wrapped in a class User.
So either:
Change the json the structure to
{
"id": "",
"user": {
"name": "",
"age": ""
}
}
Or
Unwrap the User class, the Employee class will be:
public class Employee
{
#JsonProperty("Id")
private Integer Id;
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private String age;
}
Edit
If you can't choose either option 1 or 2, you have only one option left is to create custom deserializer:
Write a deserializer:
public class EmployeeDeserializer extends StdDeserializer<Item> {
public EmployeeDeserializer() {
this(null);
}
public EmployeeDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Employee deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("Id")).numberValue();
String name = node.get("Name").asText();
String age = node.get("Age")).asText();
User user = new User(name, age);
return new Employee(id, user);
}
}
Then register this deserializer:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Employee.class, new EmployeeDeserializer());
mapper.registerModule(module);
Employee readValue = mapper.readValue(json, Employee.class);
Another way to register deserializer is:
#JsonDeserialize(using = EmployeeDeserializer.class)
public class Employee {
It seems you are not nesting your JSON correctly. Or your Object Structure is wrong.
JSON should be:
{
"Id": "",
"user" : {
"Name": "",
"Age": ""
}
}
The json structure does not match the structure of your classes.
if the json was like;
{
"Id": "an-id,
"user": {
"Name": "Joe",
"Age": "21"
}
}
Then your code to deserialise to an Employee object would work.
I intend to create a JSON Array with the following structure. The metadata tag is going to constant in all the entries. I am stumped.
[{
"metadata": {
"Value": "String"
},
"name": "String",
"id": "String"
},
{
"metadata": {
"Value": "String"
},
"name": "String",
"id": "String"
}
]
public class yourJsonObject {
private Map<String, String> metadata;
private String name;
private string id;
public yourJsonObject() {
}
public Map<String, String> getMetadata(){
return metadata;
}
public void setMetadata(Map<String, String> metadata){
this.metadata = metadata;
}
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId(){
return id;
}
public void setId(String id){
this.id = id;
}
}
Then somewhere else you can just do this:
ObjectMapper mapper = new ObjectMapper(); // create once, reuse
yourJsonObject example = new yourJsonObject(); // have your POJO you want to save
mapper.writeValue(new File("result.json"), example);
To read you can just use:
ObjectMapper mapper = new ObjectMapper(); // create once, reuse
yourJsonObject value = mapper.readValue(new File("data.json"), yourJsonObject .class);
Both snippets are taken from my linked wiki article from jackson themselves.
Jackson should automatically be able to parse this POJO to an equivalent JSON if configured correctly.
Note: Jackson has to be globally registered and has to know about it. Please read the wiki of what you use to know about it... Jackson in 5 Minutes
Else you could just manually build the JSON like Neeraj said.
JSONArray array = new JSONArray(); // Create JSONArray Object
JSONObject jsonObject = new JSONObject(); // Your JSONObject which gets added into array
jsonObject.put("metadata",new MetaDataCustomClass("SomeRandomStringValue"));
jsonObject.put("name", "Neeraj");
jsonObject.put("id", "123");
array.add(jsonObject); // Here you push the jsonObject into Array.
Note: MetaDataCustomClass is just a custom Class having a Value instance variable of type String.
Class MetaDataCustomClass {
private String value;
public MetaDataCustomClass(String value){
this.value = value;
}
}