§Handling and serving JSON
In Java, Play uses the Jackson JSON library to convert objects to and from JSON. Play’s actions work with the JsonNode
type and the framework provides utility methods for conversion in the play.libs.Json
API.
§Mapping Java objects to JSON
Jackson allows you to easily convert Java objects to JSON by looking at field names, getters and setters. As an example we’ll use the following simple Java object:
// Note: can use getters/setters as well; here we just use public fields directly.
// if using getters/setters, you can keep the fields `protected` or `private`
public static class Person {
public String firstName;
public String lastName;
public int age;
}
We can parse the JSON representation of the object and create a new Person
:
Person person = new Person();
person.firstName = "Foo";
person.lastName = "Bar";
person.age = 30;
JsonNode personJson = Json.toJson(person); // {"firstName": "Foo", "lastName": "Bar", "age": 30}
Similarly, we can write the Person
object to a JsonNode
:
// parse the JSON as a JsonNode
JsonNode json = Json.parse("{\"firstName\":\"Foo\", \"lastName\":\"Bar\", \"age\":13}");
// read the JsonNode as a Person
Person person = Json.fromJson(json, Person.class);
§Handling a JSON request
A JSON request is an HTTP request using a valid JSON payload as request body. Its Content-Type
header must specify the text/json
or application/json
MIME type.
By default an action uses an any content body parser, which you can use to retrieve the body as JSON (actually as a Jackson JsonNode
):
public Result sayHello(Http.Request request) {
JsonNode json = request.body().asJson();
if (json == null) {
return badRequest("Expecting Json data");
} else {
String name = json.findPath("name").textValue();
if (name == null) {
return badRequest("Missing parameter [name]");
} else {
return ok("Hello " + name);
}
}
}
public Result sayHello(Http.Request request) {
Optional<Person> person = request.body().parseJson(Person.class);
return person.map(p -> ok("Hello, " + p.firstName)).orElse(badRequest("Expecting Json data"));
}
Of course it’s way better (and simpler) to specify our own BodyParser
to ask Play to parse the content body directly as JSON:
@BodyParser.Of(BodyParser.Json.class)
public Result sayHello(Http.Request request) {
JsonNode json = request.body().asJson();
String name = json.findPath("name").textValue();
if (name == null) {
return badRequest("Missing parameter [name]");
} else {
return ok("Hello " + name);
}
}
Note: This way, a 400 HTTP response will be automatically returned for non JSON requests with Content-type set to application/json.
You can test it with curl
from a command line:
curl
--header "Content-type: application/json"
--request POST
--data '{"name": "Guillaume"}'
http://localhost:9000/sayHello
It replies with:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 15
Hello Guillaume
§Serving a JSON response
In our previous example we handled a JSON request, but replied with a text/plain
response. Let’s change that to send back a valid JSON HTTP response:
public Result sayHello() {
ObjectNode result = Json.newObject();
result.put("exampleField1", "foobar");
result.put("exampleField2", "Hello world!");
return ok(result);
}
Now it replies with:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"exampleField1":"foobar","exampleField2":"Hello world!"}
You can also return a Java object and have it automatically serialized to JSON by the Jackson library:
public Result getPeople() {
List<Person> people = personDao.findAll();
return ok(Json.toJson(people));
}
If you already have a JSON String
that you’d like to return, you can do that as well:
public Result sayHello() {
String jsonString = "{\"exampleField1\": \"foobar\"}";
return ok(jsonString).as(play.mvc.Http.MimeTypes.JSON);
}
§Advanced usage
There are two possible ways to customize the ObjectMapper
for your application.
§Configurations in application.conf
Because Play uses Pekko Jackson serialization support, you can configure the ObjectMapper
based on your application needs. The documentation for jackson-databind Features explains how you can further customize JSON conversion process, including Mapper, Serialization and Deserialization features.
If you would like to use Play’s Json
APIs (toJson
/fromJson
) with a customized ObjectMapper
, you need to add the custom configurations in your application.conf
. For example, if you want to add a new module for Joda types
pekko.serialization.jackson.play.jackson-modules += "com.fasterxml.jackson.datatype.joda.JodaModule"
Or to add set a serialization configuration:
pekko.serialization.jackson.play.serialization-features.WRITE_NUMBERS_AS_STRINGS=true
§Custom binding for ObjectMapper
If you still want to take completely over the ObjectMapper
creation, this is possible by overriding its binding configuration. You first need to disable the default ObjectMapper
module in your application.conf
play.modules.disabled += "play.core.ObjectMapperModule"
Then you can create a provider for your ObjectMapper
:
public class JavaJsonCustomObjectMapper implements Provider<ObjectMapper> {
@Override
public ObjectMapper get() {
ObjectMapper mapper =
new ObjectMapper()
// enable features and customize the object mapper here ...
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// Needs to set to Json helper
Json.setObjectMapper(mapper);
return mapper;
}
}
And bind it via Guice as an eager singleton so that the ObjectMapper
will be set into the Json
helper:
public class JavaJsonCustomObjectMapperModule extends AbstractModule {
@Override
protected void configure() {
bind(ObjectMapper.class).toProvider(JavaJsonCustomObjectMapper.class).asEagerSingleton();
}
}
Afterwards enable the Module:
play.modules.enabled += "path.to.JavaJsonCustomObjectMapperModule"
Next: Working with XML