How to extract a single JSON property in Spring Boot Application? - java

I am feeling kind of lost since I already struggled with some dependencies in my pom.xml, but what I am trying to do is to extract a single property from a JSON in my Spring Boot Application.
I have a FilmServiceClient, declared as FeignClient which fetches some data from IMDb, which looks as following:
{
"Title": "Kingsman: The Secret Service",
"Year": "2014",
"Rated": "R",
[...]
"Metascore": "60",
"imdbRating": "7.7",
"imdbVotes": "597,264",
[...]
}
In my FilmService, I would like to implement a getRating method which extracts the imdbRating from the JSON as double attribute, which I eventually want to add to the Film entity in my DB.
I am grateful for every advise as well as the necessary dependencies and imports, thank you in advance!

Thanks to paulsm4, I studied the information from baeldung.com/jackson and could extract the necessary data perfectly :)
If interested, this is how my code looks like now:
protected String getRating(String title) throws Exception{
String rating;
String film = fsc.getFilmInfo(title);
try{
ObjectMapper mapper = new ObjectMapper();
JsonNode tree = mapper.readTree(film);
JsonNode node = tree.get("imdbRating");
rating = node.textValue();
}
catch(Exception e){
rating = "0.0";
}
return rating;
}
As result, it returns "7.7" as String as stated in the JSON above.
Thanks and have a good night!

You can convert json string to object as below. Then you can access properties of film object.
Here Film is POJO with same properties defined in json string
import com.fasterxml.jackson.databind.ObjectMapper;
Film film = new ObjectMapper().readValue(jsonString, Film.class);
Film POJO
public class Film {
private String title;
private double imdbRating;
[...]
// getters and setters
}

You can do:
ObjectMapper objectMapper = new ObjectMapper();
double imdbRating = objectMapper.readTree(json).findValue("imdbRating").asDouble();
System.out.println(imdbRating);
Output:
7.7
If you don't seem to have ObjectMapper, you can add the dependency in pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.1</version>
</dependency>

Related

Deserializing into class with specified key from response in jackson

Im getting response from external api
"success": true,
"data": [
{}
I'd like to map only data and it's corresponding array as entire class.
Right now I have wrapper for it but it is +1 class just for that.
public class YYYYYY {
private boolean success;
#JsonProperty(value = "data")
private List<PipeDriveContact> arrayData;
This is similar to https://stackoverflow.com/a/19097149/6785908
You'll first need to get the array
String jsonStr = "{\"success\": true,\"data\": [{\"test\": \"some data\"}]}";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonStr);
ArrayNode arrayNode = (ArrayNode) node.get("data");
System.out.println(arrayNode);
List<PipeDriveContact> pojos = mapper.readValue(arrayNode.toString(), new TypeReference<List<PipeDriveContact>>() {});
System.out.println(pojos);
prints (with a toString())
[{\"test\": \"some data\"}] // the json array
But trust me, unless you have a very compelling reason (than "I don't want one more class"), I would discourage you from heading down this path, instead implement it with the wrapper class and call it done.
Reason: In future you may generate your Pojos from a contract (swagger spec / ol JSON schema), or you may find some use for the "success" field.
If you absolutely don't need the other keys in the outermost object, you could parse out the array against the key "data" and then parse it separately into your POJO. Following is my rough implementation:
First, parse out the data array:
String json = "{\"success\": true,\"data\": [{\"test\": \"some data\"}]}";
JSONObject obj = new JSONObject(json);
String data = obj.getJSONArray("data").toString();
Then, using Jackson (or anything else), create an ArrayList with your required objects:
ObjectMapper objectMapper = new ObjectMapper();
TypeReference<ArrayList<PipeDriveContact>> typeRef = new TypeReference<ArrayList<PipeDriveContact>>() {};
ArrayList<PipeDriveContact> dataArray = objectMapper.readValue(data, typeRef);
Following is the model POJO I created for testing:
public class PipeDriveContact {
private String test;
public String getTest() { return test; }
public void setTest(String test) { this.test = test; }
}
Following are the dependencies I used:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20171018</version>
</dependency>
Hope this helps.
Either you can use #JsonCreator for defining constructor and populate only data
Or you can also use Custom de-serializer too.
Further reading and examples:
http://www.baeldung.com/jackson-annotations
http://buraktas.com/convert-objects-to-from-json-by-jackson-example/
https://codexplo.wordpress.com/2015/11/26/custom-json-deserialization-with-jackson/
Maybe it's not exactly what you are looking for, but you can map only the fields you need in your class with #JsonIgnoreProperties(ignoreUnknown = true) :
#JsonIgnoreProperties(ignoreUnknown = true)
public class YYYYYY {
#JsonProperty(value = "data")
private List<PipeDriveContact> arrayData;

Jackson adds backslash in json

I'm building REST service on Jersey and using Jackson to produce JSON from java classes of my model. Model with absolutely simple values, I think this is the most typical case. But I get strange result:
[{\"name\":\"Nick\",\"role\":\"admin\",\"age\":\"32\",\"rating\":47}]
My expecting result:
[{"name":"Nick","role":"admin","age":"32","rating":47}]
My source values of fields does NOT contains any special characters. These are simple words.
There're my Java classes.
Entity:
public class User {
private String name;
private String role;
private String age;
private Integer rating;
Class of rest service:
#ServiceConfig(contextName = "myContext")
#Path("/myrest")
public class MyRestService {
private static final String JSON_CONTENT_TYPE = MediaType.APPLICATION_JSON + ";charset=UTF-8";
#Context
protected HttpServletResponse response;
#GET
#Path("/users")
#OpenTransaction
#Produces({MediaType.APPLICATION_JSON})
public String findUsers(#QueryParam("department") String department) {
response.setContentType(JSON_CONTENT_TYPE);
PDTResponse.status(response).sendStatus(Response.Status.OK.getStatusCode());
List<User> users = new ArrayList<>();
users.add(new User("Nick", "admin", "32", 47));
String jsonInString;
ObjectMapper mapper = new ObjectMapper();
try {
jsonInString = mapper.writeValueAsString(users);
} catch (JsonProcessingException ex) {
jsonInString = "thrown exception: " + ex.getMessage();
}
return jsonInString;
}
I've tried to use annotation #JsonRawValue for string properties:
#JsonRawValue
private String name;
But result in this case was:
[{\"name\":Nick,\"role\":admin,\"age\":32,\"rating\":47}]
And I expect:
[{"name":"Nick","role":"admin","age":"32","rating":47}]
It's obvious that Jackson somehow escapes the quotes in result json of response. But why does it do it, and most importantly how to avoid that? By themselves they are just strings! Without any quotes or special characters.
I use Java 7 and Jackson 2.6.1. And Postman to test result.
Any ideas for fix of my problem?
You can configure the ObjectMapper:
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
String jsonUsers = mapper.writeValueAsString(users);
more info here
All strings in java have to escape quotes in them. So jsonInString should have slashes in it. When you output jsonInString though it shouldn't have the quotes. Are you looking at it in a debugger or something?
Do this.
ObjectMapper mapper = new ObjectMapper();
mapper.getFactory().setCharacterEscapes(new JsonUtil().new CustomCharacterEscapes());
ObjectWriter writer = mapper.writer();
String jsonDataObject = mapper.writeValueAsString(configMap);
public class CustomCharacterEscapes extends CharacterEscapes {
private final int[] _asciiEscapes;
public CustomCharacterEscapes() {
_asciiEscapes = standardAsciiEscapesForJSON();
//By default the ascii Escape table in jackson has " added as escape string
//overwriting that here.
_asciiEscapes['"'] = CharacterEscapes.ESCAPE_NONE;
}
#Override
public int[] getEscapeCodesForAscii() {
return _asciiEscapes;
}
#Override
public SerializableString getEscapeSequence(int i) {
return null;
}
}
If you are using Spring and the #ControllerAdvice for JSONP, then create a wrapper for the JSON string and use #JsonRawValue on the property. The JSONP #ControllerAdvice will not wrap a String response, it needs an Object.
public class JsonStringResponse {
#JsonValue
#JsonRawValue
private String value;
public JsonStringResponse(String value) {
this.value = value;
}
}
#GetMapping
public ResponseEntity<JsonStringResponse> getJson() {
String json = "{"id":2}";
return ResponseEntity.ok().body(new JsonStringResponse(json));
}
#ControllerAdvice
public class JsonpControllerAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpControllerAdvice() {
super("callback");
}
}
Response is a json object {"id":2}
If there is a callback parameter the response is callbackparameter({"id":2});
Looks like you are over complicating your JAX-RS resource class.
To use Jackson as a JSON provider for Jersey 2.x, you don't need to create an ObjectMapper instance like that. There's a better way to achieve it. Keep reading for more details.
Adding Jackson module dependencies
To use Jackson 2.x as your JSON provider you need to add jersey-media-json-jackson module to your pom.xml file:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
Registering the Jackson module
Then register the JacksonFeature in your Application / ResourceConfig subclass:
#ApplicationPath("/api")
public class MyApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(JacksonFeature.class);
return classes;
}
}
#ApplicationPath("/api")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(JacksonFeature.class);
}
}
If you don't have an Application / ResourceConfig subclass, you can register the JacksonFeature in your web.xml deployment descriptor. The specific resource, provider and feature fully-qualified class names can be provided in a comma-separated value of jersey.config.server.provider.classnames initialization parameter.
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>org.glassfish.jersey.jackson.JacksonFeature</param-value>
</init-param>
The MessageBodyWriter provided by Jackson is JacksonJsonProvider. For more details on how to use Jackson as a JSON provider, have a look at this answer. If you need to customize the ObjectMapper, refer to this answer.
Fixing your resource class
By using the approach described above, you resource class can be as simple as:
#Path("/users")
public class MyRestService {
#GET
#Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
public List<User> findUsers() {
List<User> users = new ArrayList<>();
users.add(new User("Nick", "admin", "32", 47));
return Response.ok(users).build();
}
When requesting such endpoint, it will give you the expected JSON as result.
I have also the same problem and tried different solutions, but non works. The problem is not with the mapper, but with the input to the mapper. As in your case:
jsonInString = mapper.writeValueAsString(users); 'users' is a collection. You need to convert each user to JSONObject, add it to JSONArray and then use the mapper on the array: like this
JSONArray users = new JSONArray();
for (Collection user : usersCollection) {
JSONObject user = new JSONObject(mapper.writeValueAsString(user));
users.put(user);
}
mapper.writeValueAsString(user));
I don't know why, but in my case it works doing this :
private static final String COOKIE_TEMPLATE = "{0}={1};Version={2};Domain={3};Max-Age={4};Path='/'";
response.addHeader("Set-Cookie", MessageFormat.format(COOKIE_TEMPLATE, cookie.getName(),cookie.getValue(), cookie.getVersion(), cookie.getDomain(),Integer.toString(cookie.getMaxAge())));
return ResponseEntity.ok(...);
cookie is a javax.servlet.http.Cookie, and cookie.getValue() contains a string produced by
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
If I use
response.addCookie(cookie)
I have a resulting cookie definition as JSON with backslashes.
But, if I use
response.addHeader("Set-Cookie",MessageFormat(TEMPLATE,cookie.get...))
I managed the same resulting cookie definition as JSON, but without backslashes.
In case of having several cookies, addHeader("Set-Cookie") only creates/updates the desired cookie. The other ones are maintained and won't be altered.
public class StateDate{
#JsonRawValue
Boolean state;
#JsonRawValue
String date;
public String toJson() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false);
try {
return mapper.writeValueAsString(this);
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
I've faced similar issue, Following configuration will help sort the issue:
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, false);
For some people who still need an answer if struggling
Try adding #JsonRawValue to the field.
The #JsonRawValue annotation can instruct Jackson to serialize a property exactly as is.
Even I came across this problem today and I stumbled across this question. People have provided multiple ways of removing the backslashes, but the thing is that the problem goes down to the very essence of what we are trying to do here.
We want to return the json response of an api call, but we are returning it as a JSONString formatted in way so that it can be printed, read and understood in Java. When you print it it looks exactly the way you want it to be when you return it.
Long story short, you must return the bytes from the function, not a String. Change the return type to byte[] and return this:
new ObjectMapper().writeValueAsString(response).getBytes(StandardCharset.UTF_8);
This will give you the purest JSON you ever want to read. Mostly, people face this issue when on the other side they are reading from an InputStream and are unable to map it to the same class and it does not work. This is how you'll fix it.
It should not be a problem, just you need to parse it in javascript and use it : JSON.parse(response)

JSON unmarshalling using JAX-RS and MOXy

I'm implementing RESTFul web service using Jersey 2.22.1 with MOXY as Json Provider.
For example I have the following entity User:
public class User {
private String id;
private String email;
private Address address;
private List<Phone> phones;
// getters & setters
}
and additional classes
public class Address {
private String type;
private String value;
// getters & setters
}
public class Phone {
private String type;
private String value;
// getters & setters
}
This is my JAX-RS resource implementation:
#POST
public Response create(User user) {
// some logic
}
Now when I'm sending POST request containting following json data:
{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
it works perfectly, MOXY automatically maps this json to user object and it's fine
But I need to handle json with another level of nesting, like this:
{
"user":{
"id":"qwe12",
"email":"emailname#g-mail.com",
"address":{
"type":"1WHEN-Honorable",
"value":"1WHEN-M"
},
"phones":[
{
"type":"HOME",
"number":"034-2342-12-31"
},
{
"type":"WORK",
"number":"31-21-3211-32"
}
]
}
}
As you can see there is another key called user, and I know it's not a good json structure but it's a requirement and I have to accept it as it is. Now I need to be able to handle it. For now I can see only one solution.
I can add another one class wrapper aroung User and pass it to the create method.
So it would look this:
JAX-RS resource:
#POST
public Response create(UserWrapper user) {
// some logic
}
And java class:
public class UserWrapper {
private User user;
// getters & setters
}
It's working solution but I don't really like it because I need to add one more additional class. Would like to here your suggestions how to keep my java classes as it is and be able to accept json with one more level of nesting (i mean this user key).
Thanks in advance!
May not be the answer you're looking for, but I recommend using Jackson instead of MOXy. It's a more mature JSON framework with more features, and just works better. There may be a way with MOXy, but here is the Jackson way
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.1</version>
</dependency>
<!-- You need to remember to remove MOXy -->
In a ContextResolver, configure the ObjectMapper to unwrap the root value
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper mapper;
public MyObjectMapperProvider() {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
}
private static ObjectMapper createDefaultMapper() {
return mapper;
}
}
The value it will look for to deserialize will either be
The value in a #JsonRootName annotation, e.g. #JsonRootName("user") (on the class)
The value in a #XmlRootElement annotation, e.g. #XmlRootElment(name="user") (on the class)
If there is no annotation, then the name of the class, with the first letter lower cased.
Also not, unless you are using any MOXy specific features, making the switch to Jackson, you probably will not need to make any changes at all to your classes. Jackson also supports JAXB annotations (for the most part).
If you want the response to be wrapped, you can also use
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

How to easily and smoothly get data from data which should be of MediaType.JSON in Java?

Is it a nice practice to retrieve a JSON object as a String and then parse it manually inside the application or there is a better way to get a transfer object representation (eg. some tools or comfortable APIs, automated mapping services, don't know)?
Example:
#POST
#Path("/myUrlPath")
public Response postSomething(String jsonAsString) {
JSON json = getFromMyCustomParser(jsonAsString);
MyObject myObject = getFromMyCustomMapper(json);
//business logic
}
Don't know much about this topic.
You can actually accept a JSON as a parameter of your resource method. The Jersey REST API would support this. You might have to add a JSON library as a dependency.
I think that this is the JSON dependency we use:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.12</version>
</dependency>
In this case we use the JSONObject class from org.codehaus.jettison.json.
There's a tutorial on how to do this using the Jersey framework. It explains POJO mapping which is based on Jackson. You may have to configure POJO mapping yourself.
Heres an example for Jackson:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(new Something("Name")));
public class Something {
private String name;
public Something(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Output: {"name":"Name"}
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.3.1</version>
</dependency>
Further reading: Jackson
Example for org.json.JSONObject:
JSONObject json = new JSONObject();
List<Object> list = new ArrayList<>();
list.add("Hello");
list.add("Hello2");
list.add("Hello3");
json.put("List", list);
System.out.println(json.toString());
Output: {"List":["Hello","Hello2","Hello3"]}
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
Further reading: JSON.org
Example for GSON
(https://sites.google.com/site/gson/gson-user-guide#TOC-Object-Examples)
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
Deserialization
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
Output: {"value1":1,"value2":"abc"}
Further reading: GSON

Exclude empty Arrays from Jackson ObjectMapper

I am building JSON from Java object tree using Jackson ObjectMapper. Some of my Java objects are collections and sometimes they might be empty. So if they are empty that ObjectMapper generates me: "attributes": [], and I want to exclude those kind of empty JSON arrays from my result. My current ObjectMapper config:
SerializationConfig config = objectMapper.getSerializationConfig();
config.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
config.set(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
From this post I've read that I can use:
config.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
But that is generating me an error:
Caused by: java.lang.IllegalArgumentException: Class com.mycomp.assessments.evaluation.EvaluationImpl$1 has no default constructor; can not instantiate default bean value to support 'properties=JsonSerialize.Inclusion.NON_DEFAULT' annotation.
So how should I prevent those empty arrays to appear in my result?
You should use:
config.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY);
for Jackson 1 or
config.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
for Jackson 2
A very good example describing :
JsonInclude.Include.NON_NULL
JsonInclude.Include.ABSENT
JsonInclude.Include.NON_EMPTY
In : https://www.logicbig.com/tutorials/misc/jackson/json-include-non-empty.html
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Employee {
private String name;
private String dept;
private String address;
private List<String> phones;
private AtomicReference<BigDecimal> salary;
.............
}
public class ExampleMain {
public static void main(String[] args) throws IOException {
Employee employee = new Employee();
employee.setName("Trish");
employee.setDept("");
employee.setAddress(null);
employee.setPhones(new ArrayList<>());
employee.setSalary(new AtomicReference<>());
ObjectMapper om = new ObjectMapper();
String jsonString = om.writeValueAsString(employee);
System.out.println(jsonString);
}
}
=====> Result :
If we don't use #JsonInclude annotation at all then output of the above example will be:
{"name":"Trish","dept":"","address":null,"phones":[],"salary":null}
If we use #JsonInclude(JsonInclude.Include.NON_NULL) on Employee class then output will be:
{"name":"Trish","dept":"","phones":[],"salary":null}
If we use #JsonInclude(JsonInclude.Include.NON_ABSENT) then output will be:
{"name":"Trish","dept":"","phones":[]}
If we use #JsonInclude(JsonInclude.Include.NON_EMPTY) :
{"name":"Trish"}
If you can modify the object to be serialized, you can also place an annotation directly on the field, for example (Jackson 2.11.2):
#JsonProperty
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<String> mySet = new HashSet<>();
In this way, no further configuration of the ObjectMapper is required.

Categories