DEV Community

loading...
Cover image for Parsing JSON in Spring Boot, part 1

Parsing JSON in Spring Boot, part 1

scottshipp profile image scottshipp Originally published at code.scottshipp.com ・7 min read

Welcome to a quick tutorial on different JSON to Java use cases in Spring Boot. If you have an existing Spring Boot application, you can add the classes listed below to it and follow along. If you don’t have an existing Spring Boot application, consider following the tutorial Build a next-level RESTful web service with Spring Boot first, in order to get to a place where you can experiment with the following.

Simple JSON object to mutable Java object

Perhaps the most common use case is allowing someone to send an HTTP POST request to an endpoint, and for the posted JSON to be converted automatically to a plain-old Java object (POJO). Here is an example that “just works” with Spring Boot and Jackson.

The JSON for this example is:

{
    "brand": "Apple",
    "model": "iPhone"
}

The corresponding Java object would be:

And the corresponding controller:

You can use a tool like Postman or curl to POST the JSON. Make sure you send the “Content-Type” header with the value “application/json” so that Spring Boot automatically parses it into an instance of Smartphone. Here’s what that would look like as a curl command:

curl --location --request POST 'localhost:8080/smartphone' \
--header 'Content-Type: application/json' \
--data-raw '{
    "brand": "Apple",
    "model": "iPhone"
}'

Once that request is sent, the application should predictably route the request to the controller, match the request to the Java object, parse it, and then, log the following:

Received new smartphone: Apple iPhone

Simple JSON object to immutable Java object

The same use case as above can be achieved with an immutable object. Immutable objects are better for use in multithreaded code and they also have the benefit of promoting more maintainable code in general. You can read more about these benefits in my article about immutable objects.

Here is an immutable version of the above Smartphone class:

Notice that the only differences between the mutable and immutable version of Smartphone.java are as follows:

  1. The member variables are declared final.

  2. There are no setters.

  3. The constructor is annotated with the “@JsonCreator” annotation.

As you can see, it’s easy to create an immutable class to represent your JSON.

By changing the Smartphone.java class to this new version, but keeping the existing SmartphoneController.java class, you should be able to make the same POST request as before and observe the same result.

Because of their benefits, I will use immutable objects throughout the remainder of this article.

JSON object holding another JSON object

Sometimes, an object has another object within it. Treating the contained object as a static inner class of the POJO closely models this relationship between the objects.

Consider if the smartphone example had a “model” that was not a single String, but was instead another object with a model name and/or a model version:

{
    "brand": "Apple",
    "model": {
        "name": "iPhone",
        "version": "11 Pro"
    }
}

It is common to represent “Model” as a static inner class. No other changes to how you treat that class are necessary. The inner class can be represented as either an immutable or mutable object. Here is the immutable version of Smartphone with an immutable static inner class to represent Model:

If you use this version of Smartphone.java with your service, make sure and update the information that SmartphoneController.java logs so that you can see all the data that is passed. Here’s an example curl that will POST the above object:

curl --location --request POST 'localhost:8080/smartphone' \
--header 'Content-Type: application/json' \
--data-raw '{
    "brand": "Apple",
    "model": {
        "name": "iPhone",
        "version": "11 Pro"
    }
}'

Graph paper and pen

Post a JSON object with an array

What if someone adds a “features” attribute to this JSON, which is an array of strings describing different features of each phone? Like this example:

{
    "brand": "Apple",
    "model": {
        "name": "iPhone",
        "version": "11 Pro"
    },
    "features": ["Super Retina XDR display", "12 MP camera", "Up to 512 GB capacity"]
}

Jackson can parse a JSON array into either an array or a collection, like a List. We can take our existing Smartphone.java from the prior example and add a new features member with type List<String>.

No change needs to be made to the controller at all, but to help you visualize the result, try adding a log statement to the addSmartPhone method in order to log the features received, like line 4 here:

With that code in place, recompile and run the application. When you post the JSON above to the /smartphone endpoint, the application will log:

Received new smartphone: Apple iPhone 11 Pro
The features of the smartphone are Super Retina XDR display, 12 MP camera, Up to 512 GB capacity

A volcano against Aurora Borealis

Post a JSON array to the endpoint

Sometimes the JSON posted is not an object, but an array. Let’s say that we would like a new endpoint on our service that can receive multiple smartphones at once. JSON would look like this:

[{
    "brand": "Apple",
    "model": {
        "name": "iPhone",
        "version": "11 Pro"
    },
    "features": ["Super Retina XDR display", "12 MP camera", "Up to 512 GB capacity"]
},
{
    "brand": "Samsung",
    "model": {
        "name": "Galaxy",
        "version": "S20"
    },
    "features": ["6.2\" screen", "64MP camera", "4000 mAh battery"]
}]

We need to add a new method to our SmartphoneController to take the list of phones. Since both will now share the capability of logging the object received, we refactor that out into a private method. We now have this:

Our POJO in Smartphone.java doesn’t change at all.

The key things to notice about the new “addSmartphones” method is that the relevant types have changed:

  • The method parameter “smartphones” is of type List

  • The return type is ResponseEntity<List<Smartphone>>.

  • When you post the JSON provided above to this endpoint (make sure you post to /smartphones not just /smartphone) you will then see the expected logging:

Received new smartphone: Apple iPhone 11 Pro
The features of the smartphone are Super Retina XDR display, 12 MP camera, Up to 512 GB capacity
Received new smartphone: Samsung Galaxy S20
The features of the smartphone are 6.2" screen, 64MP camera, 4000 mAh battery

Wrapped JSON object

Sometimes the JSON object is wrapped in a thin JSON object “wrapper.” Taking our original smartphone object, wrapping it would be to make that object itself an attribute of an outer object like this:

{
    "smartphone": {
        "brand": "Apple",
        "model": {
            "name": "iPhone",
            "version": "11 Pro"
        },
        "features": [
            "Super Retina XDR display",
            "12 MP camera",
            "Up to 512 GB capacity"
        ]
    }
}

The obvious way to handle JSON like that is to create another class, SmartphoneWrapper.java, that represents the wrapper:

You could even make Smartphone a static inner class of this wrapper.

There’s a small wrinkle with this approach which is that all resulting code is now complicated with “reaching through” the wrapper to the smartphone inside. For example, the SmartphoneController.addSmartphone method might end up looking like this now:

From the controller’s standpoint, we don’t care about that wrapper. We might want to throw it out when we receive JSON like that, and keep dealing with just the object inside.

Jackson offers a feature to handle this called root element wrapping. Unfortunately, to my knowledge, it can only be turned on or off at the application level. So first, you might add the following settings to the application.properties of your Spring Boot application:

spring.jackson.serialization.wrap-root-value=true
spring.jackson.deserialization.unwrap-root-value=true

With those settings applied to the application, we now need to tell Jackson that the “smartphone” attribute in our JSON holds the smartphone object. To do that, we go to Smartphone.java and add the annotation “@JsonRootName(“smartphone”).” Everything else is still the same in Smartphone.java:

You don’t need to change the SmartphoneController. It should look like this still:

Try rebuilding the application now and test it out. Send the JSON from the start of this section as your request body. Everything works out great!

The drawback to root wrapping

Because of that change in settings, and the fact that it takes place across the entire application, any other request bodies sent to endpoints in other parts of the application will always have to be wrapped. If you attempt to use a POJO like we’ve been using throughout the rest of this article in another controller, you will see an exception similar to the following:

[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Root name 'brand' does not match expected ('smartphone') for type [simple type, class com.scottshipp.code.restservice.Smartphone]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'brand' does not match expected ('smartphone') for type [simple type, class com.scottshipp.code.restservice.Smartphone]
 at [Source: (PushbackInputStream); line: 2, column: 5] (through reference chain: com.scottshipp.code.restservice.Smartphone["brand"])]

For that reason, I prefer not to use root wrapping and just put up with the “reaching through” problem of a custom wrapper object. Your mileage may vary.

Conclusion

I hope this walkthrough of common Spring Boot JSON parsing use cases was useful. Did I miss any? Was anything unclear? Any other comments or questions? Let me know in the comment section below.


This article originally appeared at code.scottshipp.com

Discussion

pic
Editor guide