Jackson Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course (COVID-pricing ends in January):

>> CHECK OUT THE COURSE

1. Overview

Apache Camel is a powerful open-source integration framework implementing a number of the known Enterprise Integration Patterns.

Typically when working with message routing using Camel, we'll want to use one of the many supported pluggable data formats. Given that JSON is popular in most modern APIs and data services, it becomes an obvious choice.

In this tutorial, we'll take a look at a couple of ways we can unmarshal a JSON Array into a list of Java objects using the camel-jackson component.

2. Dependencies

First, let’s add the camel-jackson dependency to our pom.xml:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jackson</artifactId>
    <version>3.6.0</version>
</dependency>

Then, we'll also add the camel-test dependency specifically for our unit tests, which is available from Maven Central as well:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-test</artifactId>
    <version>3.6.0</version>
</dependency>

3. Fruit Domain Classes

Throughout this tutorial, we'll use a couple of light POJO objects to model our fruit domain.

Let's go ahead and define a class with an id and a name to represent a fruit:

public class Fruit {

    private String name;
    private int id;

    // standard getter and setters
}

Next, we'll define a container to hold a list of Fruit objects:

public class FruitList {

    private List<Fruit> fruits;

    public List<Fruit> getFruits() {
        return fruits;
    }

    public void setFruits(List<Fruit> fruits) {
        this.fruits = fruits;
    }
}

In the next couple of sections, we'll see how to unmarshal a JSON string representing a list of fruits into these domain classes. Ultimately what we are looking for is a variable of type List<Fruit> that we can work with.

4. Unmarshalling a JSON FruitList

In this first example, we're going to represent a simple list of fruit using JSON format:

{
    "fruits": [
        {
            "id": 100,
            "name": "Banana"
        },
        {
            "id": 101,
            "name": "Apple"
        }
    ]
}

Above all, we should emphasize that this JSON represents an object which contains a property called fruits, which contains our array.

Now let's set up our Apache Camel route to perform the deserialization:

@Override
protected RouteBuilder createRouteBuilder() throws Exception {
    return new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:jsonInput")
              .unmarshal(new JacksonDataFormat(FruitList.class))
              .to("mock:marshalledObject");
        }
    };
}

In this example, we use direct endpoint with the name jsonInput. Next, we call the unmarshal method, which unmarshals the message body on our Camel exchange using the specified data format.

We're using the JacksonDataFormat class with a custom unmarshal type of FruitList. This is essentially a simple wrapper around the Jackon ObjectMapper and lets us marshal to and from JSON.

Finally, we send the result of the unmarshal method to a mock endpoint called marshalledObject. As we're going to see, this is how we'll test our route to see if it is working correctly.

With that in mind, let's go ahead and write our first unit test:

public class FruitListJacksonUnmarshalUnitTest extends CamelTestSupport {

    @Test
    public void givenJsonFruitList_whenUnmarshalled_thenSuccess() throws Exception {
        MockEndpoint mock = getMockEndpoint("mock:marshalledObject");
        mock.expectedMessageCount(1);
        mock.message(0).body().isInstanceOf(FruitList.class);

        String json = readJsonFromFile("/json/fruit-list.json");
        template.sendBody("direct:jsonInput", json);
        assertMockEndpointsSatisfied();

        FruitList fruitList = mock.getReceivedExchanges().get(0).getIn().getBody(FruitList.class);
        assertNotNull("Fruit lists should not be null", fruitList);

        List<Fruit> fruits = fruitList.getFruits();
        assertEquals("There should be two fruits", 2, fruits.size());

        Fruit fruit = fruits.get(0);
        assertEquals("Fruit name", "Banana", fruit.getName());
        assertEquals("Fruit id", 100, fruit.getId());

        fruit = fruits.get(1);
        assertEquals("Fruit name", "Apple", fruit.getName());
        assertEquals("Fruit id", 101, fruit.getId());
    }
}

Let's walk through the key parts of our test to understand what is going on:

  • First things first, we start by extending the CamelTestSupport class –  a useful testing utility base class
  • Then we set up our test expectations. Our mock variable should have one message, and the message type should be a FruitList
  • Now we're ready to send out the JSON input file as a String to the direct endpoint we defined earlier
  • After we check our mock expectations have been satisfied, we are free to retrieve the FruitList and check the contents is as expected

This test confirms that our route is working properly and our JSON is being unmarshalled as expected. Awesome!

5. Unmarshalling a JSON Fruit Array

On the other hand, we want to avoid using a container object to hold our Fruit objects. We can modify our JSON to hold a fruit array directly:

[
    {
        "id": 100,
        "name": "Banana"
    },
    {
        "id": 101,
        "name": "Apple"
    }
]

This time around, our route is almost identical, but we set it up to work specifically with a JSON array:

@Override
protected RouteBuilder createRouteBuilder() throws Exception {
    return new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:jsonInput")
              .unmarshal(new ListJacksonDataFormat(Fruit.class))
              .to("mock:marshalledObject");
        }
    };
}

As we can see, the only difference to our previous example is that we're using the ListJacksonDataFormat class with a custom unmarshal type of Fruit. This is a Jackson data format type prepared directly to work with lists.

Likewise, our unit test is very similar:

@Test
public void givenJsonFruitArray_whenUnmarshalled_thenSuccess() throws Exception {
    MockEndpoint mock = getMockEndpoint("mock:marshalledObject");
    mock.expectedMessageCount(1);
    mock.message(0).body().isInstanceOf(List.class);

    String json = readJsonFromFile("/json/fruit-array.json");
    template.sendBody("direct:jsonInput", json);
    assertMockEndpointsSatisfied();

    @SuppressWarnings("unchecked")
    List<Fruit> fruitList = mock.getReceivedExchanges().get(0).getIn().getBody(List.class);
    assertNotNull("Fruit lists should not be null", fruitList);

    // more standard assertions
}

However, there are two subtle differences with respect to the test we saw in the previous section:

  • We're first setting up our mock expectation to contain a body with a List.class directly
  • When we retrieve the message body as a List.class, we'll get a standard warning about type safety – hence the use of @SuppressWarnings(“unchecked”)

6. Conclusion

In this short article, we've seen two simple approaches for unmarshalling JSON arrays using camel message routing and the camel-jackson component.

As always, the full source code of the article is available over on GitHub.

Jackson bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course (COVID-pricing ends in January):

>> CHECK OUT THE COURSE
Comments are closed on this article!