[
{
"address":{
"addr":"modified - addr1"
},
"id":1,
"name":"user 1",
"registration":"2018-12-29T18:13:40.028"
},
{
"address":{
"addr":"modified - addr2"
},
"id":2,
"name":"user 2",
"registration":"2018-12-29T18:13:40.028"
}
]
Serializador personalizado JSON-B
Este exemplo mostra como personalizar a serialização/desserialização de objetos com jsonb para um aplicativo JAX-RS.
Executando e testando o Endpoint
A Aplicação pode ser executado com `mvn clean install tomee:run' se porta 8080 está disponível, você pode invocar o seguinte endpoint: (GET) http://localhost:8080/jsonb-custom-serializer/api/users que deve responder com o seguinte json:
e o endpoint: (POST) http://localhost:8080/jsonb-custom-serializer/api/users com um corpo como:
{
"id": 1,
"name": "name1",
"extra": "extra1"
}
que respondem com o seguinte json:
{
"id": 1,
"name": "name1extra1",
"registration": "Sat Dec 29 19:48:05 CET 2018",
"address": null
}
@ApplicationPath
Classe JAXRS de ponto de entrada, da seguinte maneira, o jaxrs carregará todas as classes e métodos anotados com @Path sem especificá-los.
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("api")
public class JAXRSApplication extends Application {
}
Recurso @Path Rest
Classe jaxrs simples com um endpoint GET e um POST
import java.util.ArrayList;
import java.util.List;
import jakarta.ejb.Stateless;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.superbiz.model.Address;
import org.superbiz.model.User;
@Path("users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Stateless
public class UserService {
@GET
public List<User> users() {
List<User> users = new ArrayList<>();
User user1 = new User(1, "user 1", new Address("addr1"));
User user2 = new User(2, "user 2", new Address("addr2"));
users.add(user1);
users.add(user2);
return users;
}
@POST
public User addUser(User u) {
// Apenas para mostrar a desserialização
return u;
}
}
Configuração JSONB
Implementando ContextResolver<> você pode personalizar os padrões jaxrs; neste exemplo, personalizaremos a serialização JSONB para todos os objetos do tipo Address, definidos na classe AddressSerializer
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
import jakarta.ws.rs.ext.ContextResolver;
import jakarta.ws.rs.ext.Provider;
@Provider
public class JSONBConfiguration implements ContextResolver<Jsonb> {
private Jsonb jsonb;
public JSONBConfiguration() {
JsonbConfig config = new JsonbConfig().withFormatting(true).withSerializers(new AddressSerializer());
jsonb = JsonbBuilder.create(config);
}
@Override
public Jsonb getContext(Class<?> type) {
return jsonb;
}
}
Address serializer
Classe serializadora de objetos simples que adiciona modified durante a serialização
import jakarta.json.bind.serializer.JsonbSerializer;
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;
import org.superbiz.model.Address;
public class AddressSerializer implements JsonbSerializer<Address> {
@Override
public void serialize(Address obj, JsonGenerator generator, SerializationContext ctx) {
if (obj != null) {
obj.setAddr("modified - " + obj.getAddr());
ctx.serialize(obj, generator);
}
}
}
User Deserializer
Cria um objeto a partir de um json
import java.lang.reflect.Type;
import jakarta.json.JsonObject;
import jakarta.json.bind.serializer.DeserializationContext;
import jakarta.json.bind.serializer.JsonbDeserializer;
import jakarta.json.stream.JsonParser;
import org.superbiz.model.User;
public class UserDeserializer implements JsonbDeserializer<User> {
@Override
public User deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
JsonObject jo = parser.getObject();
String name = jo.get("name").toString().replace("\"", "");
if (jo.get("extra") != null) {
name = name + jo.get("extra").toString().replace("\"", "");
}
User u = new User(Integer.parseInt(jo.get("id").toString()), name, null);
return u;
}
}
Usando o desserializador @JsonbTypeDeserializer @JsonbTypeSerializer
Com a anotação @JsonbTypeDeserializer ou @JsonbTypeSerializer, você pode notificar o jsonb para usar o desserializador personalizado
import java.util.Date;
import jakarta.json.bind.annotation.JsonbTypeDeserializer;
import org.superbiz.UserDeserializer;
@JsonbTypeDeserializer(UserDeserializer.class)
public class User {
private Integer id;
private String name;
private Date registration = new Date();
private Address address;
public User(Integer id, String name, Address address) {
super();
this.id = id;
this.name = name;
this.address = address;
}
public User() {
super();
}
// ... GET/SET
}
Acessando o endpoint rest
O teste implanta o aplicativo da web no TomEE embedded com o Arquillian e chama o endpoint do usuário
package org.superbiz.rest;
import java.io.IOException;
import java.net.URL;
import jakarta.ws.rs.core.MediaType;
import org.apache.cxf.jaxrs.client.WebClient;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.superbiz.AddressSerializer;
import org.superbiz.JAXRSApplication;
import org.superbiz.JSONBConfiguration;
import org.superbiz.UserDeserializer;
import org.superbiz.model.Address;
import org.superbiz.model.User;
@RunWith(Arquillian.class)
public class UserServiceTest {
@ArquillianResource
private URL base;
@Deployment(testable = false)
public static WebArchive app() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(UserService.class, JAXRSApplication.class, JSONBConfiguration.class,
AddressSerializer.class, UserDeserializer.class, Address.class, User.class);
}
@Test
public void get() throws IOException {
final String message = WebClient.create(base.toExternalForm()).path("api/users").get(String.class);
Assert.assertTrue(message.contains("modified - addr1"));
}
@Test
public void post() throws IOException {
final String inputJson = "{ \"id\": 1, \"name\": \"user1\", \"extra\": \"extraField\"}";
final User responseUser = WebClient.create(base.toExternalForm()).path("api/users")
.type(MediaType.APPLICATION_JSON).post(inputJson, User.class);
Assert.assertTrue(!responseUser.getName().equals("user1"));
Assert.assertTrue(responseUser.getName().equals("user1" + "extraField"));
}
}
Executando
A execução do exemplo pode ser feita no maven com um simples comando `mvn clean install', executada no diretório `jsonb-custom-serializer'.
Ao executar, você verá uma saída semelhante à seguinte.
Running org.superbiz.rest.JsonbRecordMethodVisibilityTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.099 s -- in org.superbiz.rest.JsonbRecordMethodVisibilityTest
Running org.superbiz.rest.UserServiceTest
INFO: Starting TomEE from: /tmp/arquillian-apache-tomee-24081
INFO: Starting Servlet engine: [Apache Tomcat/11.0.25]
INFO: Starting ProtocolHandler ["http-nio-52997"]
INFO: Configuring enterprise application: /path/to/tomee/examples/jsonb-custom-serializer/target/tomee-embedded/0/f603a20e-e090-463d-9547-20b127a58e2d
INFO: Enterprise application "/path/to/tomee/examples/jsonb-custom-serializer/target/tomee-embedded/0/f603a20e-e090-463d-9547-20b127a58e2d" loaded.
INFO: Deployed Application(path=/path/to/tomee/examples/jsonb-custom-serializer/target/tomee-embedded/0/f603a20e-e090-463d-9547-20b127a58e2d)
INFO: Provider{clazz=org.superbiz.JSONBConfiguration, discovered=true, singleton=false}
INFO: REST Application: http://localhost:52997/f603a20e-e090-463d-9547-20b127a58e2d/api -> org.superbiz.JAXRSApplication
INFO: Service URI: http://localhost:52997/f603a20e-e090-463d-9547-20b127a58e2d/api/users -> EJB org.superbiz.rest.UserService
INFO: GET http://localhost:52997/f603a20e-e090-463d-9547-20b127a58e2d/api/users -> List<User> users()
INFO: POST http://localhost:52997/f603a20e-e090-463d-9547-20b127a58e2d/api/users -> User addUser(User)
INFO: Undeploying app: /path/to/tomee/examples/jsonb-custom-serializer/target/tomee-embedded/0/f603a20e-e090-463d-9547-20b127a58e2d
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.856 s -- in org.superbiz.rest.UserServiceTest
Results:
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
Dentro do jar
tomee-plus-api traz todas as dependências necessárias para iniciar uma aplicação REST funcionando.
Se olharmos para o jar construído pelo maven, veremos que a aplicação:
$ jar tvf target/jsonb-custom-serializer-10.0.0-M1-SNAPSHOT.war
0 Sat Dec 29 19:10:44 CET 2018 META-INF/
134 Sat Dec 29 19:10:42 CET 2018 META-INF/MANIFEST.MF
0 Sat Dec 29 19:10:42 CET 2018 WEB-INF/
0 Sat Dec 29 19:10:42 CET 2018 WEB-INF/classes/
0 Sat Dec 29 19:10:42 CET 2018 WEB-INF/classes/org/
0 Sat Dec 29 19:10:42 CET 2018 WEB-INF/classes/org/superbiz/
0 Sat Dec 29 19:10:42 CET 2018 WEB-INF/classes/org/superbiz/model/
0 Sat Dec 29 19:10:42 CET 2018 WEB-INF/classes/org/superbiz/rest/
790 Sat Dec 29 19:10:38 CET 2018 WEB-INF/classes/org/superbiz/model/Address.class
2093 Sat Dec 29 19:10:38 CET 2018 WEB-INF/classes/org/superbiz/model/User.class
2063 Sat Dec 29 19:10:38 CET 2018 WEB-INF/classes/org/superbiz/UserDeserializer.class
402 Sat Dec 29 19:10:38 CET 2018 WEB-INF/classes/org/superbiz/JAXRSApplication.class
1461 Sat Dec 29 19:10:38 CET 2018 WEB-INF/classes/org/superbiz/AddressSerializer.class
1498 Sat Dec 29 19:10:38 CET 2018 WEB-INF/classes/org/superbiz/rest/UserService.class
1549 Sat Dec 29 19:10:38 CET 2018 WEB-INF/classes/org/superbiz/JSONBConfiguration.class
1241 Sat Dec 29 17:52:48 CET 2018 WEB-INF/web.xml
0 Sat Dec 29 19:10:44 CET 2018 META-INF/maven/
0 Sat Dec 29 19:10:44 CET 2018 META-INF/maven/org.superbiz/
0 Sat Dec 29 19:10:44 CET 2018 META-INF/maven/org.superbiz/jsonb-custom-serializer/
1811 Sat Dec 29 17:53:36 CET 2018 META-INF/maven/org.superbiz/jsonb-custom-serializer/pom.xml
132 Sat Dec 29 19:10:42 CET 2018 META-INF/maven/org.superbiz/jsonb-custom-serializer/pom.properties
Esse simples jar poderia ser implantado em qualquer implementação Java EE compliant.
No TomEE você simplesmente colocaria-o no diretório tomee.home/webapps/ .