How to send null values using jaxrs-jsonb (JSON-B | JSR-367)? - java

I am working in a legacy system with Java/EJB/JPA/thorntail/wildfly.
The project has:
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>jaxrs-jsonb</artifactId>
<version>2.6.0.Final</version>
</dependency>
JSON-B provides support for JSON Processing according to JSR-367.
I need to send null values in API/Rest. Anything similar to #JsonInclude(Include.ALWAYS) for Jackson.
Example that I need:
PersonApi with name=Any, age=null
{
"name": "Any",
"age": null
}
Is there some config to do this? ... some annotation, some config to load and resolve it?

You can use javax.json.bind.annotation.JsonbNillable annotation:
#JsonbNillable
public static class PersonApi {
private String name;
private Integer age;
//constructors, getters and setters are omitted
}

Related

How to serialise a POJO to be sent over a Java RSocket?

I'm trying to send a POJO over an RSocket requestStream:
import java.io.Serializable;
class GreetingRequest implements Serializable {
private String name;
public GreetingRequest() {}
public GreetingRequest(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
If I were to send a String I can do:
ByteBuf data = ByteBufAllocator.DEFAULT.buffer().writeBytes("Hello".getBytes());
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
But how can I serialise my POJO?
This is my attempt using implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' which doesn't work:
GreetingRequest pojo = new GreetingRequest("Davide");
ByteBuf data = SerializationUtils.serialize(pojo);
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
There is a native java serialization mechanism that I would NOT recommend, but you can read about it. Read about Serialazable interface in Java API. There are 2 options that I would recommend:
JSON-JACKSON (also known as Faster XML)
GSON (mentioned in the answer from CĂ©sar Ferreira)
Both convert classes to JSON and vise-versa. For JSON-JACKSON see class ObectMapper. In particular methods writeValueAsString() or writeValueAsBytes() to serialize your object to JSON string or bytes. And to convert it back look for method readValue().
Here are the Maven artifacts that you would need to use it:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.3</version>
</dependency>
I recommend you using Gson converter. It helps you to convert a Java Class to a JSON String. And then you can work with the String as if you were working with simple text.
You can import the dependency:
dependencies {
implementation 'com.google.code.gson:gson:2.8.7'
}
And then, can use jsonschema2pojo to convert the JSON:
{ "name": "Test" }
to classes like this:
package com.example;
import javax.annotation.Generated;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class GreetingRequest {
#SerializedName("name")
#Expose
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
After all is done you can do something like this in Java:
Gson converter = new Gson();
GreetingsRequest request = new GreetingRequest();
request.setName("Test");
String greetingsJSON = converter.toJson(request);
And then you can still send the JSON string as it follows:
ByteBuf data = ByteBufAllocator.DEFAULT.buffer().writeBytes(greetingsJSON.getBytes());
socket.requestStream(DefaultPayload.create(data, metadata))
.map(Payload::getDataUtf8)
.toIterable()
.forEach(System.out::println);
Data conversions:
JSON Object - Java class
Array - List<>
Helpful links:
This is the library you need to include (tutorials included) in Java: GSON Converter Git
This is an JSON to Class online converter: Jsonschema2pojo generator
If you use a framework like Spring Boot this is taken care of for you. You may want to manually control in which case the other examples are more relevant, but there are productivity benefits to Spring Boot or rsocket-rpc.
https://github.com/rsocket/rsocket-demo/blob/master/src/main/kotlin/io/rsocket/demo/chat/ChatController.kt
https://spring.io/blog/2020/03/02/getting-started-with-rsocket-spring-boot-server
or rsocket-rpc-java using protobuf instead of Serialization
https://github.com/rsocket/rsocket-rpc-java/blob/master/docs/get-started.md

Make Spring Boot JSON enum deserialization strict, so it does not silently convert invalid values into null

I am using Java Spring Boot #RestController with an object containing enum fields.
Spring automagically deserializes the JSON to the MyRequest object.
#RestController
public class MyController {
#PostMapping(path = "/operation")
public ResponseEntity<MyResponse> operation(#Valid #RequestBody MyRequest request) {
...
}
}
public class MyRequest {
private MyEnum1 field1;
private MyEnum2 field2;
private MyEnum3 field3;
private MyEnum4 field4;
private MyEnum5 field5;
private MyEnum6 field6;
... // really a lot of various enum fields!
}
public enum MyEnum1 {
VAL1, VAL2, VAL3;
}
The problem is that if the JSON contains completely invalid value of the enum field, the deserializer silently converts them to null, without any exception.
{
"field1": "BLAHBLAH",
...
}
This is user-unfriendly and treacherous.
I know that I may write custom JSON deserializers for each enum, but the solution is cumbersome and non-elegant.
Is there a way to globally set the JSON enum deserializer to a "strict mode", so if the value is invalid it throws an exception? If so, how and where?
That feature should be disabled by default.
But if you want to set it explicitly you can do it like this:
in your properties:
spring.jackson.deserialization.read-unknown-enum-values-as-null=false
or as an alternative in a configuration class (actually any bean would work, just make sure it happens early):
#Autowired
public void configureJackson(ObjectMapper objectMapper) {
objectMapper.disable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}
Because it should actually be like this by default, I am wondering why it is not for you. Do you enable it somewhere? Which Spring Boot version are you using?

Similarity in Elasticsearch properties in Springframework

I need to use property "similarity" in my elasticsearch index, but cannot find the property in the Field annotation of Springframework. It seems, the Springframework Elasticsearch library don't have that. Do I need to use another library or are there simple ways to do that? Can you recommend a library or a way?
You can use the Setting annotation and define your settings in a separate file, with the appropriate configuration for similarity.
To perform this, just add #Setting(settingPath = "/path/to/settings.json") to your index class, where you have #Document annotation.
e.g.
#Setting(settingPath = "/path/to/settings.json")
#Document(indexName = "indexName")
public class IndexClass {
#Id
private String id;
private String name;
// getters and setters
}
And your settings.json should look like :
"index": {
"similarity": {
"my_similarity": {
"type": "DFR",
"basic_model": "g",
"after_effect": "l",
"normalization": "h2",
"normalization.h2.c": "3.0"
}
}
}
And you can find more in the documentation.

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 use some indirection when unmarshalling json to java bean using Jersey using jaxb annotations

I'm trying to unmarshall some received json (from Jira restful web service).
Problem is: an "issue" has a "summary" property and a list of fields.
Summary is not present as an attribute in the received json, but as a value of the "fields" attribute. I insist on unmarshalling to this structure:
#XmlRootElement
class Issue {
String summary;
List<Field> fields;
// getters/setters and lots of other fields
}
Received JSON:
{
"expand":"html",
"self":"https://example.com/jira/rest/api/latest/issue/XYZ-1234",
"key":"XYZ-1234",
"fields":
{
"summary":
{
"name":"summary",
"type":"java.lang.String",
"value":"test 1234"
},
"customfield_10080":
{
"name":"Testeur",
"type":"com.atlassian.jira.plugin.system.customfieldtypes:userpicker"
},
"status":
{
"name":"status",
"type":"com.atlassian.jira.issue.status.Status",
"value":
{
"self":"https://example.com/jira/rest/api/latest/status/5",
"name":"Resolved"
}
},
...
},
"transitions":"https://example.com/jira/rest/api/latest/issue/XYZ-1234/transitions"
}
I don't want to use Jira's own client (too many dependencies which I don't want in my app).
edit: I asked my question another way to try to make it clear: how to map a bean structure to a different schema with jax-rs
Your annotated class should be bijective: it should allow to generate the same input from which it was unmarshalled. If you still want to use a non-bijective object graph, you can use #XmlAnyElement the following way:
public class Issue {
#XmlElement(required = true)
protected Fields fields;
public Fields getFields() {
return fields;
}
}
In the input you gave, fields is not a list, but a field (JSON uses [] to delimit lists):
public class Fields {
#XmlElement(required = true)
protected Summary summary;
#XmlAnyElement
private List<Element> fields;
public List<Element> getFields() {
return fields;
}
public Summary getSummary() {
return summary;
}
}
In order to catch Summary, you'll have to define a dedicated class. Remaining fields will be grouped in the fields list of elements.
public class Summary {
#XmlAttribute
protected String name;
public String getName() {
return name;
}
}
Below, a unit test using your input shows that everything work:
public class JaxbTest {
#Test
public void unmarshal() throws JAXBException, IOException {
URL xmlUrl = Resources.getResource("json.txt");
InputStream stream = Resources.newInputStreamSupplier(xmlUrl).getInput();
Issue issue = parse(stream, Issue.class);
assertEquals("summary", issue.getFields().getSummary().getName());
Element element = issue.getFields().getFields().get(0);
assertEquals("customfield_10080", element.getTagName());
assertEquals("name", element.getFirstChild().getLocalName());
assertEquals("Testeur", element.getFirstChild().getFirstChild().getTextContent());
}
private <T> T parse(InputStream stream, Class<T> clazz) throws JAXBException {
JSONUnmarshaller unmarshaller = JsonContextNatural().createJSONUnmarshaller();
return unmarshaller.unmarshalFromJSON(stream, clazz);
}
private JSONJAXBContext JsonContextNatural() throws JAXBException {
return new JSONJAXBContext(JSONConfiguration.natural().build(), Issue.class);
}
}
This tests shows that without using dedicated classes, your code will quickly be hard to read.
You will need those maven dependencies to run it:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>r08</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.6</version>
</dependency>
{
"expand":"html",
"self":"xxx/jira/rest/api/latest/issue/EPC-2731";,
"key":"EPC-2731",
"fields":{
"summary":{
"name":"summary",
"type":"java.lang.String",
"value":"Fwd: commentaires vides dans FicheSousGroupement"
},
"timetracking":{
"name":"timetracking",
"type":"com.atlassian.jira.issue.fields.TimeTrackingSystemField",
"value":{
"timeestimate":0,
"timespent":60
}
},
"issuetype":{
"name":"issuetype",
"type":"com.atlassian.jira.issue.issuetype.IssueType",
"value":{
"self":"xxx/jira/rest/api/latest/issueType/2";,
"name":"Nouvelle fonctionnalité",
"subtask":false
}
},
"customfield_10080":{
"name":"Testeur",
"type":"com.atlassian.jira.plugin.system.customfieldtypes:userpicker"
},

Categories