Chapter 4. JSON in Java

We’ve shown how to use JSON with JavaScript and Ruby on Rails, and we’ll now move to Java, our third and final platform for this book. Here’s what we’ll cover:

  • Performing Java/JSON serialization/deserialization with Jackson

  • Working with Java Objects and JSON

  • Using JSON with JUnit

  • Making RESTful API calls and testing the results with JUnit and JsonUnit

  • Building a small JSON-based API with Spring Boot

In our examples, we’ll make RESTful API calls to work with the data we deployed on json-server in the previous chapter. We’ll then move to create a more realistic JSON-based Web API. Before we develop a RESTful API, we need to start with the basics of Java serialization/deserialization with JSON, and then add more complexity.

Java and Gradle Setup

This chapter uses Gradle for building source and test code. If you haven’t installed Java and Gradle, go to Appendix A and see “Install the Java Environment” and “Install Gradle”. After that, you will have a basic environment that enables you to run the examples.

Gradle Overview

Gradle leverages the concepts from earlier Java-based build systems—Apache Ant and Maven. Gradle is widely used and provides the following functionality for Java projects:

  • Project structure (a common/standard project directory structure)

  • Dependency Management (for JAR files)

  • A common build process

The gradle init utility initializes a project by creating a core directory structure and some initial implementations for the build script, along with simple Java source and test code. Here are the key directories and files in a Gradle project:

  • src/main/ contains source code and resources.

    • java/ is the Java source code.

    • resources/ contains the resources (e.g., properties, data files—JSON in our case) used by the source code.

  • test/main/ contains source code and resources.

    • java/ is the Java source code.

    • resources/ contains the resources (e.g., properties, data files—JSON in our case) used by the source code.

  • build/ contains the .class files generated by compiling the source and test code.

    • libs/ contains the JAR or WAR files that result from building the project.

  • gradlew is the Gradle wrapper that enables you to run a project as an executable JAR. We’ll cover this in more detail in the Spring Boot section later.

  • build.gradle is initiated for you by gradle init, but you need to fill it in with project-specific dependencies. Gradle uses a Groovy-based DSL for its build scripts (rather than XML).

  • build/ contains build-related artifacts created by gradle build or gradle test.

Here are the most important Gradle tasks you’ll need to know in order to work with Gradle. You can see these tasks when you type gradle tasks on the command line:

gradle build

Build the project.

gradle classes

Compile Java source code.

gradle clean

Delete the build directory.

gradle jar

Compile Java source code and package it (along with Resources) into a JAR.

gradle javadoc

Generate JavaDoc documentation from the Java source code.

gradle test

Run Unit Tests (includes Java source and test code compile).

gradle testClasses

Compile Java test code.

Here’s how the example projects were created:

  • gradle init --type java-application was used to create the initial speakers-test and speakers-web applications.

  • The generated build.gradle file and the Java application and test files are stubs. They have been replaced with actual code for the examples in this chapter.

Gradle is well-documented, and here are some tutorials and references to help you go deeper:

Now that we’ve covered the basics of Gradle, it’s time to look at Java-based JSON libraries, and then move on to coding examples.

Just Enough Unit Testing with JUnit

JUnit is a widely used Unit-Testing framework. The tests in this chapter use JUnit because of its common acceptance in the Java community. JUnit tests are procedural, so the Unit Tests are TDD-style. If you’d like to combine JUnit with BDD, Cucumber is a solid choice. To learn more about BDD and Cucumber in Java, see Micha Kops’ excellent article on “BDD Testing with Cucumber, Java and JUnit”.

Java-Based JSON Libraries

There are several solid JSON libraries for Java/JSON serialization/deserialization, including these:

Jackson

You can find details about Jackson in the GitHub repository.

Gson

Gson is provided by Google.

JSON-java

This library is provided by Doug Crockford.

Java SE (Standard Edition)

JSON support was introduced into the Java platform in JavaEE 7 as part of the Java Specification Request (JSR) 353 initiative. JSR-353 is a standalone implementation, and you can integrate it with your Java SE applications as of Java SE 8. Java SE 9 will provide native JSON support as part of the Java Enhancement Proposal (JEP) 198 initiative.

All examples in this chapter use Jackson because it

  • Is widely used (especially by the Spring community)

  • Provides excellent functionality

  • Has worked well for a long time

  • Is well maintained with an active development community

  • Has good documentation

Additionally, we’ll maintain focus by sticking with one Java/JSON library. As mentioned, the other libraries work well, so feel free to try them on your own.

Let’s start with the basics of Java serialization/deserialization.

JSON Serialization/Deserialization with Jackson

Java applications need to convert from Java data structures to JSON (serialize) and convert from JSON to Java (deserialize).

Serialization/Deserialization with Simple Java Data Types

As in previous chapters, we’ll start by serializing some basic Java data types:

  • integer

  • string

  • array

  • boolean

Example 4-1 shows a simple Unit Test that uses Jackson and JUnit 4 to serialize/deserialize simple Java data types.

Example 4-1. speakers-test/src/test/java/org/jsonatwork/ch4/BasicJsonTypesTest.java
package org.jsonatwork.ch4;

import static org.junit.Assert.*;

import java.io.*;
import java.util.*;

import org.junit.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.*;
import com.fasterxml.jackson.databind.*;

public class BasicJsonTypesTest {
  private static final String TEST_SPEAKER = "age = 39\n" +
    "fullName = \"Larson Richard\"\n" +
    "tags = [\"JavaScript\",\"AngularJS\",\"Yeoman\"]\n" +
    "registered = true";

  @Test
  public void serializeBasicTypes() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      Writer writer = new StringWriter();
      int age = 39;
      String fullName = new String("Larson Richard");
      List<String> tags = new ArrayList<String>(
          Arrays.asList("JavaScript", "AngularJS", "Yeoman"));

      boolean registered = true;
      String speaker = null;

      writer.write("age = ");
      mapper.writeValue(writer, age);
      writer.write("\nfullName = ");
      mapper.writeValue(writer, fullName);
      writer.write("\ntags = ");
      mapper.writeValue(writer, tags);
      writer.write("\nregistered = ");
      mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
      mapper.writeValue(writer, registered);
      speaker = writer.toString();
      System.out.println(speaker);
      assertTrue(TEST_SPEAKER.equals(speaker));
      assertTrue(true);
    } catch (JsonGenerationException jge) {
      jge.printStackTrace();
      fail(jge.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

  @Test
  public void deSerializeBasicTypes() {
    try {
      String ageJson = "{ \"age\": 39 }";
      ObjectMapper mapper = new ObjectMapper();
      Map<String, Integer> ageMap = mapper.readValue(ageJson,
          new TypeReference<HashMap<String,Integer>>() {});

      Integer age = ageMap.get("age");

      System.out.println("age = " + age + "\n\n\n");
      assertEquals(39, age.intValue());
      assertTrue(true);
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

}

In this example, the @Test annotation tells JUnit to run the serializeBasicTypes() and deSerializeBasicTypes() methods as part of the test. These Unit Tests don’t do many assertions on the JSON data itself. We’ll cover assertions in more detail later when we test against a Web API.

Here are the most important Jackson classes and methods that serialize/deserialize to/from JSON:

  • ObjectMapper converts between Java and JSON constructs.

  • ObjectMapper.writeValue() converts a Java data type to JSON (and in this case, outputs to a Writer).

  • ObjectMapper.readValue() converts JSON to a Java data type.

Run a single Unit Test from the command line as follows:

cd chapter-4/speakers-test

+gradle test --tests org.jsonatwork.ch4.BasicJsonTypesTest+

You should see these results:

json 04in01

This example isn’t too exciting right now because it serializes/deserializes only simple data types to/from JSON. Serialization/deserialization gets more interesting when Objects are involved.

Serialization/Deserialization with Java Objects

Now that we have a decent grasp of Jackson and how to work with simple Java data types, let’s wade in deeper with Objects. Example 4-2 shows how to use Jackson to serialize/deserialize a single speaker Object, and then how to deserialize a JSON file into multiple speaker Objects.

Example 4-2. speakers-test/src/test/java/org/jsonatwork/ch4/SpeakerJsonFlatFileTest.java
package org.jsonatwork.ch4;

import static org.junit.Assert.*;

import java.io.*;
import java.net.*;
import java.util.*;

import org.junit.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.type.*;

public class SpeakerJsonFlatFileTest {

private static final String SPEAKER_JSON_FILE_NAME = "speaker.json";
private static final String SPEAKERS_JSON_FILE_NAME = "speakers.json";
private static final String TEST_SPEAKER_JSON = "{\n" +
    "  \"id\" : 1,\n" +
    "  \"age\" : 39,\n" +
    "  \"fullName\" : \"Larson Richard\",\n" +
    "  \"tags\" : [ \"JavaScript\", \"AngularJS\", \"Yeoman\" ],\n" +
    "  \"registered\" : true\n" +
  "}";

@Test
public void serializeObject() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      Writer writer = new StringWriter();
      String[] tags = {"JavaScript", "AngularJS", "Yeoman"};
      Speaker speaker = new Speaker(1, 39, "Larson Richard", tags, true);
      String speakerStr = null;

      mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
      speakerStr = mapper.writeValueAsString(speaker);
      System.out.println(speakerStr);
      assertTrue(TEST_SPEAKER_JSON.equals(speakerStr));
      assertTrue(true);
    } catch (JsonGenerationException jge) {
      jge.printStackTrace();
      fail(jge.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

  private File getSpeakerFile(String speakerFileName) throws URISyntaxException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL fileUrl = classLoader.getResource(speakerFileName);
    URI fileUri = new URI(fileUrl.toString());
    File speakerFile = new File(fileUri);

    return speakerFile;
  }

  @Test
  public void deSerializeObject() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      File speakerFile = getSpeakerFile(
          SpeakerJsonFlatFileTest.SPEAKER_JSON_FILE_NAME);

      Speaker speaker = mapper.readValue(speakerFile, Speaker.class);

      System.out.println("\n" + speaker + "\n");
      assertEquals("Larson Richard", speaker.getFullName());
      assertEquals(39, speaker.getAge());
      assertTrue(true);
    } catch (URISyntaxException use) {
      use.printStackTrace();
      fail(use.getMessage());
    } catch (JsonParseException jpe) {
      jpe.printStackTrace();
      fail(jpe.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

  @Test
  public void deSerializeMultipleObjects() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      File speakersFile = getSpeakerFile(
          SpeakerJsonFlatFileTest.SPEAKERS_JSON_FILE_NAME);

      JsonNode arrNode = mapper.readTree(speakersFile).get("speakers");
      List<Speaker> speakers = new ArrayList<Speaker>();
        if (arrNode.isArray()) {
          for (JsonNode objNode : arrNode) {
          System.out.println(objNode);
          speakers.add(mapper.convertValue(objNode, Speaker.class));
        }
      }

      assertEquals(3, speakers.size());
      System.out.println("\n\n\nAll Speakers\n");
      for (Speaker speaker: speakers) {
        System.out.println(speaker);
      }

      System.out.println("\n");
      Speaker speaker3 = speakers.get(2);
      assertEquals("Christensen Fisher", speaker3.getFullName());
      assertEquals(45, speaker3.getAge());
      assertTrue(true);
    } catch (URISyntaxException use) {
      use.printStackTrace();
      fail(use.getMessage());
    } catch (JsonParseException jpe) {
      jpe.printStackTrace();
      fail(jpe.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

}

Note the following in this JUnit test:

  • serializeObject() creates a Speaker Object and serializes it to Standard Output by using the ObjectMapper.writeValueAsString() method and System.out.println(). The test sets the SerializationFeature.INDENT_OUTPUT to true to indent/pretty-print the JSON output.

  • deSerializeObject() calls getSpeakerFile() to read a JSON input file (which contains a single speaker JSON Object), and uses the ObjectMapper.readValue() method to deserialize it into a Speaker Java Object.

  • deSerializeMultipleObjects() does the following:

    • Calls getSpeakerFile() to read a JSON input file, which contains an array of JSON speaker Objects.

    • Invokes the ObjectMapper.readTree() method to get a JsonNode Object, which is a pointer to the root node of the JSON document that was in the file.

    • Visits each node in the JSON tree and uses the ObjectMapper.convertValue() method to deserialize each speaker JSON object into a Speaker Java Object.

    • Prints out each Speaker Object in the list.

  • getSpeakerFile() finds a file on the classpath and does the following:

    • Gets the ContextClassLoader from the current Thread of execution.

    • Uses the ClassLoader.getResource() method to find the filename as a resource within the current classpath.

    • Constructs a File Object based on the URI of the filename.

Each of the preceding tests uses JUnit’s assertion methods to test the results of JSON serialization/deserialization.

You’ll see the following when you run the test from the command line using gradle test --tests org.jsonatwork.ch4.SpeakerJsonFlatFileTest:

json 04in02

Jackson offers much more functionality than can be shown in this chapter. Refer to the following resources for some great tutorials:

Unit Testing with a Stub API

Until now, we’ve been using JUnit to test against the data from JSON flat files. We’ll now do a more realistic test against an API. But we need an API to test against without writing a lot of code or creating lots of infrastructure. We’ll show how to create a simple Stub API (which produces a JSON response) without writing a single line of code.

Test Data

To create the Stub, we’ll use the Speaker data from earlier chapters as our test data, which is available at GitHub and deploy it as a RESTful API. We’ll leverage the json-server Node.js module to serve up the speakers.json file as a Web API. If you need to install json-server, refer to “Install npm Modules” in Appendix A. Here’s how to run json-server on port 5000 from your local machine (using a second terminal session):

cd chapter-4/speakers-test/src/test/resources

json-server -p 5000 ./speakers.json

You can also get a single speaker by adding the id to the URI as follows: http://localhost:5000/speakers/1. With the Stub API in place, it’s time to write some Unit Tests.

JSON and JUnit Testing with APIs

Our Unit Test will do the following:

  • Make HTTP calls to the Stub Speakers API

  • Check the JSON (from the HTTP Response) against expected values

As in earlier chapters, we’ll continue to leverage the open source Unirest API wrapper, but this time we’ll use the Java version.

In the previous JUnit tests in the chapter, we ensured that only bare minimum functionality was working (no exceptions were thrown), and it’s now time to make our tests a bit more sophisticated. The remaining Unit Tests will look at the JSON content returned from an HTTP Response, and verify that it matches the expected output. We could search through the data and do a comparison with custom code, or we could use a library to reduce the amount of work. JsonUnit has many helpful matchers to simplify JSON comparison in JUnit tests. We’ll cover the basics of JsonUnit in these Unit Tests, but it provides much deeper functionality than we can cover here, including the following:

  • Regular Expressions

  • More matchers

  • The ability to ignore specific fields and values

The Unit Test in Example 4-3 pulls everything together by invoking the Stub API and comparing the JSON response with expected values.

Example 4-3. speakers-test/src/test/java/org/jsonatwork/ch4/SpeakersJsonApiTest.java
package org.jsonatwork.ch4;

import static org.junit.Assert.*;

import java.io.*;
import java.net.*;
import java.util.*;

import org.apache.http.*;
import org.junit.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.request.*;

import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;

public class SpeakersApiJsonTest {
  private static final String SPEAKERS_ALL_URI = "http://localhost:5000/speakers";
  private static final String SPEAKER_3_URI = SPEAKERS_ALL_URI + "/3";

  @Test
  public void testApiAllSpeakersJson() {
    try {
      String json = null;
      HttpResponse <String> resp = Unirest.get(
        SpeakersApiJsonTest.SPEAKERS_ALL_URI).asString();

      assertEquals(HttpStatus.SC_OK, resp.getStatus());
      json = resp.getBody();
      System.out.println(json);
      assertThatJson(json).node("").isArray();
      assertThatJson(json).node("").isArray().ofLength(3);
      assertThatJson(json).node("[0]").isObject();
      assertThatJson(json).node("[0].fullName")
              .isStringEqualTo("Larson Richard");
      assertThatJson(json).node("[0].tags").isArray();
      assertThatJson(json).node("[0].tags").isArray().ofLength(3);
      assertThatJson(json).node("[0].tags[1]").isStringEqualTo("AngularJS");
      assertThatJson(json).node("[0].registered").isEqualTo(true);
      assertTrue(true);
    } catch (UnirestException ue) {
      ue.printStackTrace();
    }
  }

  @Test
  public void testApiSpeaker3Json() {
    try {
      String json = null;
      HttpResponse <String> resp = Unirest.get(
        SpeakersApiJsonTest.SPEAKER_3_URI).asString();

      assertEquals(HttpStatus.SC_OK, resp.getStatus());
      json = resp.getBody();
      System.out.println(json);
      assertThatJson(json).node("").isObject();
      assertThatJson(json).node("fullName")
              .isStringEqualTo("Christensen Fisher");
      assertThatJson(json).node("tags").isArray();
      assertThatJson(json).node("tags").isArray().ofLength(4);
      assertThatJson(json).node("tags[2]").isStringEqualTo("Maven");
      assertTrue(true);
    } catch (UnirestException ue) {
      ue.printStackTrace();
    }
  }

}

Note the following in this JUnit test:

  • testApiAllSpeakersJson():

    • Gets a list of all speakers from the Speakers API by calling Unirest.get() with http://localhost:5000/speakers

    • Verifies that the HTTP Status Code is OK (200).

    • Gets the JSON document (which contains an array of speaker Objects) from the HTTP Response Body.

    • Makes a series of assertions on the JSON document with JSONUnit’s assertThatJson() to verify that

      • We have an array of three speaker objects.

      • Each field (for example, fullName, tags, and registered) in each speaker object matches the expected values.

    • When you run gradle test, you should see the following as part of the output:

json 04in03
  • testApiSpeaker3Json():

    • Gets speaker 3 from the Speakers API by calling Unirest.get() with http://localhost:5000/speakers/3

    • Verifies that the HTTP Response Code is OK (200)

    • Gets the JSON document (which contains a single speaker Object) from the HTTP Response Body.

    • Makes a series of assertions on the JSON document with JSONUnit’s assertThatJson() to verify that

      • We have a single speaker Object.

      • Each field in the speaker Object has the expected values.

    • When you run gradle test, you should see the following as part of the output:

json 04in04

This Unit Test only touches upon the basics of the Unirest Java library, which also provides the following:

  • Full HTTP verb coverage (GET, POST, PUT, DELETE, PATCH)

  • The ability to do custom mappings from an HTTP Response Body to a Java Object

  • Asynchronous (i.e., nonblocking) requests

  • Timeouts

  • File uploads

  • And much more

Visit the Unirest website for further information on the Unirest Java library.

Before moving on, you can stop json-server by pressing Ctrl-C at the command line.

We’ve shown how to deploy and interact with a Stub API, and now it’s time to build a small RESTful API.

Build a Small Web API with Spring Boot

We’ll continue to use the Speaker data to create an API (chapter-4/speakers-api in the examples) with Spring Boot. The Spring Framework makes it easier to develop and deploy Java-based Web applications and RESTful APIs. Spring Boot makes it easier to create Spring-based applications by providing defaults. With Spring Boot:

  • There are no tedious, error-prone XML-based configuration files.

  • Tomcat and/or Jetty can be embedded, so there is no need to deploy a WAR (Web application ARchive) separately. You still could use Spring Boot and Gradle to build and deploy a WAR file to Tomcat. But as you’ll see, an executable JAR simplifies a developer’s environment because it reduces the amount of setup and installations, which enables iterative application development.

We’ll take the following steps to create and deploy the Speakers API with Spring Boot:

  1. Write source code:

    • Model

    • Controller

    • Application

  2. Create a build script (build.gradle).

  3. Deploy an embedded JAR with gradlew.

  4. Test with Postman.

Create the Model

The Speaker class in Example 4-4 is a Plain Old Java Object (POJO) that represents the Speaker data that the API will render as JSON.

Example 4-4. speakers-api/src/main/java/org/jsonatwork/ch4/Speaker.java
package org.jsonatwork.ch4;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Speaker {
  private int id;
  private int age;
  private String fullName;
  private List<String> tags = new ArrayList<String>();
  private boolean registered;

  public Speaker() {
    super();
  }

  public Speaker(int id, int age, String fullName, List<String> tags,
                 boolean registered) {
    super();
    this.id = id;
    this.age = age;
    this.fullName = fullName;
    this.tags = tags;
    this.registered = registered;
  }

  public Speaker(int id, int age, String fullName, String[] tags,
                 boolean registered) {
    this(id, age, fullName, Arrays.asList(tags), registered);
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getFullName() {
    return fullName;
  }

  public void setFullName(String fullName) {
    this.fullName = fullName;
  }

  public List<String> getTags() {
    return tags;
  }

  public void setTags(List<String> tags) {
    this.tags = tags;
  }

  public boolean isRegistered() {
    return registered;
  }

  public void setRegistered(boolean registered) {
    this.registered = registered;
  }

  @Override
  public String toString() {
    return String.format(
      "Speaker [id=%s, age=%s, fullName=%s, tags=%s, registered=%s]",
      id, age, fullName, tags, registered);
  }

}

There’s nothing exciting in this code. It just provides the data members, constructors, and accessor methods (getters and setters) for a speaker. This code doesn’t know anything about JSON because (as you’ll soon see) Spring automatically converts this object to JSON.

Create the Controller

In a Spring application, the Controller handles the HTTP Requests and returns HTTP Responses. In our case, the speaker JSON data is returned in the response body. Example 4-5 shows the SpeakerController.

Example 4-5. speakers-api/src/main/java/org/jsonatwork/ch4/SpeakerController.java
package org.jsonatwork.ch4;

import java.util.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;

@RestController
public class SpeakerController {

  private static Speaker speakers[] = {
    new Speaker(1, 39, "Larson Richard",
                new String[] {"JavaScript", "AngularJS", "Yeoman"}, true),
    new Speaker(2, 29, "Ester Clements",
                new String[] {"REST", "Ruby on Rails", "APIs"}, true),
    new Speaker(3, 45, "Christensen Fisher",
                new String[] {"Java", "Spring", "Maven", "REST"}, false)
  };

  @RequestMapping(value = "/speakers", method = RequestMethod.GET)
  public List<Speaker> getAllSpeakers() {
    return Arrays.asList(speakers);
  }

  @RequestMapping(value = "/speakers/{id}", method = RequestMethod.GET)
  public ResponseEntity<?>  getSpeakerById(@PathVariable long id) {
    int tempId = ((new Long(id)).intValue() - 1);

    if (tempId >= 0 && tempId < speakers.length) {
      return new ResponseEntity<Speaker>(speakers[tempId], HttpStatus.OK);
    } else {
      return new ResponseEntity(HttpStatus.NOT_FOUND);
    }
  }
}

Note the following in this code:

  • The @RestController annotation identifies the SpeakerController class as a Spring MVC Controller that processes HTTP Requests.

  • The speakers array is hardcoded, but it’s for test purposes only. In a real application, a separate Data Layer would populate the speakers from a database or an external API call.

  • The getAllSpeakers() method does the following:

    • Responds to HTTP GET requests on the /speakers URI.

    • Retrieves the entire speakers Array as an ArrayList and returns it as a JSON Array in an HTTP Response Body.

    • The @RequestMapping annotation binds the /speakers URI to the getAllSpeakers() method for an HTTP GET Request.

  • The getSpeakerById() method does the following:

    • Responds to HTTP GET requests on the /speakers/{id} URI (where id represents a speaker ID).

    • Retrieves a speaker (based on the speaker ID) and returns it as a JSON Object in an HTTP Response Body.

    • The @PathVariable annotation binds the speaker ID from the HTTP Request path to the id parameter for lookup.

    • The ResponseEntity return value type enables you to set the HTTP Status Code and/or the speakers in the HTTP Response.

In both of the preceding methods, the Speaker Object is automatically converted to JSON without any extra work. By default, Spring is configured to use Jackson behind the scenes to do the Java-to-JSON conversion.

Register the Application

As mentioned earlier, we could package the Speakers API as a WAR file and deploy it on an application server such as Tomcat. But it’s easier to run our API as a standalone application from the command line. To do this we need to do the following:

  • Add a Java main() method

  • Package the application as an executable JAR

The Application class in Example 4-6 provides the main() method that we need.

Example 4-6. speakers-api/src/main/java/org/jsonatwork/ch4/Application.java
package org.jsonatwork.ch4;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

In this example, the @SpringBootApplication annotation registers our application with Spring and wires up the SpeakerController and Speaker.

That’s all the code that we need. Now, let’s look at the build.gradle script to build the application.

Write the Build Script

Gradle uses a script called build.gradle to build an application. Example 4-7 shows the build script for the speakers-api project.

Example 4-7. speakers-api/build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
  }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

ext {
  jdkVersion = "1.8"
}

sourceCompatibility = jdkVersion
targetCompatibility = jdkVersion

tasks.withType(JavaCompile) {
  options.encoding = 'UTF-8'
}

jar {
  baseName = 'speakers-api'
  version =  '0.0.1'
}

repositories {
  mavenCentral()
}

test {
  testLogging {
    showStandardStreams = true // Show standard output & standard error.
  }
  ignoreFailures = false
}

dependencies {
  compile (
    [group: 'org.springframework.boot', name: 'spring-boot-starter-web']
  )
}

Note the following in this build.gradle script:

  • The Spring Boot Gradle plug-in does the following:

    • Packages all build artifacts into a single, executable JAR

    • Searches for a class in src/main/java that has a main() method (in this case, Application.java) to deploy our API within the executable JAR

  • The jar block defines the name of the application’s JAR file

  • repositories tells Gradle to pull application dependencies from the Maven Central Repository.

  • testLogging tells Gradle to show Standard Output and Standard Error when running tests.

  • dependencies defines the JARs that the speakers-api depends on.

This is a simple build, but Gradle has far more powerful build functionality. Visit the “Wiring Gradle Build Scripts” section of the Gradle User Guide to learn more.

We’ve covered the build script, and now it’s time to deploy the Speakers API.

Deploy the API

The gradlew script was generated by the gradle init command that was used to create the speakers-api project. To learn more about how to create a Gradle project, see “Creating New Gradle Builds” from the Gradle User Guide.

gradlew pulls everything together and simplifies deployment by taking the following steps:

  • Invokes the build.gradle script to build the application and uses the Spring Boot plug-in to build the executable JAR

  • Deploys the Speakers API (as an executable JAR) to http://localhost:8080/speakers on an embedded (bundled) Tomcat server

In the speakers-api directory, run ./gradlew bootRun to deploy the application, and you’ll see the following (at the end of all the log messages):

json 04in05

Test the API with Postman

Now that the Speakers API is up and running, let’s test with Postman (as we did in Chapter 1) to get the first speaker. In the Postman GUI, do the following:

  1. Enter the http://localhost:8080/speakers/1 URL.

  2. Choose GET as the HTTP verb.

  3. Click the Send button.

You should see that the GET ran properly in Postman with the speaker JSON data in the HTTP Response Body text area and a 200 (OK) HTTP Status, as shown in Figure 4-1.

json 04in06
Figure 4-1. Speakers API on Postman

You can stop gradlew by pressing Ctrl-C at the command line.

As promised, development and deployment is simpler because we didn’t do any of the following:

  • Create or modify XML-based configuration metadata for Spring or Java EE (i.e., web.xml)

  • Deploy a WAR file

  • Install Tomcat

Note that we took these deployment steps to show how to set up a simple development environment for a Web API. You still need to deploy a WAR file to an application server when you move into shared (e.g., Staging, User Acceptance Testing, Production) environments so that you have the ability to tune and load-test the application.

What We Covered

We started with simple conversion between Java and JSON constructs, and then demonstrated how to call a (Stub) JSON-based Web API and test its contents with JUnit. We then finished by creating a RESTful API with Spring Boot and tested it with Postman.

What’s Next?

With the basics of JSON usage on several core platforms (JavaScript, Ruby on Rails, and Java) behind us, we’ll move deeper into the JSON ecosystem in the next three chapters:

  • JSON Schema

  • JSON Search

  • JSON Transform

In Chapter 5, we’ll show how to structure and validate JSON documents with JSON Schema.

Get JSON at Work now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.