Preloader image

This examples shows how to customize objects serialization/deserialization with jsonb for a JAX-RS Application.

Run and test Endpoint

the application can be run with `mvn clean install tomee:run' if port 8080 is available you can invoke the following endpoint: (GET) http://localhost:8080/jsonb-custom-serializer/api/users that should respond with the following json:

[
  {
    "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"
  }
]

and the endpoint: (POST) http://localhost:8080/jsonb-custom-serializer/api/users with a body like:

{
    "id": 1,
    "name": "name1",
    "extra": "extra1"
}

which respond with the following json:

{
    "id": 1,
    "name": "name1extra1",
    "registration": "Sat Dec 29 19:48:05 CET 2018",
    "address": null
}

@ApplicationPath

JAXRS entry point class, as follows jaxrs will load all the annotated @Path classes and methods without specifying them.

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("api")
public class JAXRSApplication extends Application {

}

@Path Rest resource

Simple jaxrs class with a GET and a POST endpoint

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) {
        // Just to show the deserialization
        return u;
    }
}

JSONB Configuration

Implementing ContextResolver<> you can customize jaxrs defaults, in this example we are going to customize JSONB serialization for all objects of type Address, defined in AddressSerializer class

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

Simple Object serializer class that add `modified' during serialization

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

Create an object from a 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;
    }

}

Using the deserializer @JsonbTypeDeserializer @JsonbTypeSerializer

With the annotation @JsonbTypeDeserializer or @JsonbTypeSerializer you can notify jsonb to use the custom deserializer

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

}

Accessing the rest endpoint

The test deploys the webapp to TomEE embedded with Arquillian and invokes the users endpoint

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"));
	}

}

Running

Running the example can be done from maven with a simple `mvn clean install' command run from the `jsonb-custom-serializer' directory.

When run you should see output similar to the following.

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

Inside the jar

tomee-plus-api brings in all the dependencies needed to spin up a working REST application.

If we look at the jar built by maven, we’ll see the application itself is quite small:

$ 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

This single jar could be deployed any any compliant Java EE implementation. In TomEE you’d simply place it in the tomee.home/webapps/ directory.