Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 30 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,93 @@
# Serializing and Deserializing Json
# Serializing and Deserializing Json

This is a rough example of:
* using the Moshi Json library to serialize/deserialize; and
* using the SparkJava library to run an API server.
* making HTTP requests to external APIs.

- using the Moshi Json library to serialize/deserialize; and
- using the Spring Boot library to run an API server.
- making HTTP requests to external APIs.

It should contain most of the programmatic reference you need to do Sprint 2. E.g.:
* running an API server;
* integration/system testing an API server via HTTP requests;
* serializing/deserializing classes; and
* serializing/deserializing generic classes;

- running an API server;
- integration/system testing an API server via HTTP requests;
- serializing/deserializing classes; and
- serializing/deserializing generic classes;

However, keep in mind that this code isn't _exactly_ what you need for Sprint 2. We expect you to use this as a reference, but not to borrow too heavily (you'll need to work with different types of JSON response, requests, etc.).

Historical note: 0320 had previously used Gson for serializing/deserializing Json, but Gson seems to now be in maintenance mode, and doesn't support records well. So we switched starting in Fall 2022.

## Setup
## Setup

You'll need two dependencies for handling Json: `moshi` and `moshi-adapters`, both from `com.squareup.moshi`. You should be able to copy and paste them from the `pom.xml` of this project. Similarly, you'll depend on SparkJava: `spark-core` from `com.sparkjava`. You should be able to use this pom.xml going forward!

## Demo Framing

We're building an application that serves two (disjointed) purposes: a functioning soup restaurant, and an [idea generator](https://www.youtube.com/watch?v=RtRg9BSGNMk&ab_channel=Deadaccount).
We're building an application that serves two (disjointed) purposes: a functioning soup restaurant, and a TV Show Querier.

The application is an API server that responds to "order" requests. The response is a serialized Soup. In its current form, the response always provides the first soup (if any exist). If no
recipe exists, the server replies with an error response.
recipe exists, the server replies with an error response.

The TV Show Querier should be given a keyword to search for and returns the show name and a brief summary. However, the current implementation is missing some of these aspects, so it's your job to improve the search a bit!

The idea generator works by providing any random idea for something to do today. However, it's a little too unconstrained, so it's your job to narrow down the search a bit!
## Running
## Running

You can run the example by executing the `server.edu.brown.cs.student.main.Server` class `main` method. This starts up a real webserver on your computer. By default, it's set to use port `3232`, so you should be able (while the server is running!) to send requests via `localhost:3232` in your browser. One endpoint is `order`, so `localhost:3232/order` will produce an order result (or an error). The other one is `activity` where `localhost:3232/activity` gives a random activity!
You can run the example by executing the `server.edu.brown.cs.student.main.Server` class `main` method. This starts up a real webserver on your computer. By default, it's set to use port `3232`, so you should be able (while the server is running!) to send requests via `localhost:3232` in your browser. One endpoint is `order`, so `localhost:3232/order` will produce an order result (or an error). The other one is `show` where `localhost:3232/show` gives the TV Show Squid Game!

In order to run the server, run `mvn package` in your terminal then `./run` (using Git Bash for Windows users). This will be the same as the first Sprint. Take notice when transferring this run sprint to your Sprint 2 implementation that the path of your Server class matches the path specified in the run script. Currently, it is set to execute Server at `edu/brown/cs/student/main/server/Server`. Running through terminal will save a lot of computer resources (IntelliJ is pretty intensive!) in future sprints.

There are also two test classes! Check these out for an idea of a new shape of testing!

## Exercise
## Exercise

### Code Walk

Take a minute to familiarize yourself with the project. Start at the entry point of the project `Server.java` and explore the different handlers.

### Run and Query

Run the `server.edu.brown.cs.student.main.Server` and confirm that you are able to make web queries from your browser. Visit the endpoints specified in `Server.java`. What do they return right now?
Run the `server.edu.brown.cs.student.main.Server` and confirm that you are able to make web queries from Postman. Visit the endpoints specified in `Server.java`. What do they return right now?

### Making HTTP requests
See places labeled `TODO 1` in `ActivityHandler.java` and `TODO 1.1` in `Activity.java`.

Try to find the places in the code that correspond to the Architecture Diagram Hunt from the slides.
See places labeled `TODO 1.1` in `TVShowHandler.java` and `TODO 1.2` in `TVShow.java`.

Try to find the places in the code that correspond to the Architecture Diagram Hunt from the slides.

Then, once you are familiar with the shape of the HTTP request, see if you can use the parameters of an Activity object and the different endpoints of the BoredAPI to narrow down your search a little bit. Perhaps you could search for activities by their specific key!
Then, once you are familiar with the shape of the HTTP request, see if you can use the parameters of the TVShow object and the TVMaze API endpoint to expand your search functionality. Perhaps you could search using a keyword that the user of our Server can specify, and return a summary in addition to just the show name!

### Handling and manipulating more complex data

See places labeled `TODO 2` in `OrderHandler.java`

Right now, the `order` request produces whichever Soup appears first in the menu list. How would you modify the code so someone could order a soup based on its name? Perhaps start by getting the value of the query parameter given in the class!


### Testing

What other integration tests should have been written in the `edu.brown.cs.student.main.TestSoupAPIHandlers` class?

Add at least one.
Add at least one.

## Additional Info for Sprint 2

You can deserialize a Json object into a Java object with fewer fields. E.g., if you've got a Json object with 26 different fields:
You can deserialize a Json object into a Java object with fewer fields. E.g., if you have a Json object with 26 different fields:

```json
{
"A": 1,
"B": 2,
"C": 3,
"D": 4,
...
}
```

You can deserialize this into an object with only `"A"` and `"B"` fields. This is useful when processing very large, verbose response Json from other APIs, but only need a few fields.
You can deserialize this into an object with only `"A"` and `"B"` fields. This redues complexity when you're processing very large, verbose response Json from other APIs, but you only need a few fields.

## Note about SLF4J Error

If you see the error below appear in your run console, you can feel free to ignore it. It is just warning you to use a more useful error logging tool (of which there are many that are used in industry), but we do not teach that in 32! If you are interested, you can learn more about logging [here](https://www.baeldung.com/java-logging-intro).

`SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.`
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.`
13 changes: 6 additions & 7 deletions src/main/java/edu/brown/cs/student/main/activity/Activity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,19 @@
* there are a few fields that you could filter on if you wanted!
*/
public class Activity {
private String activity;
private String type;
private int participants;
private double price;
private String key;
private String temperature;
private String wind;
private String description;
private Object forecast;

public Activity() {}

@Override
public String toString() {
return this.activity + " with " + this.participants + " people.";
return "The temperature is " + this.temperature;
}

// TODO 1.1: Replace the top function with this one below once you have modified the URI in TODO 1
// TODO 1.2: Replace the top function with this one below once you have modified the URI in TODO 1

// @Override
// public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public Object handle(Request request, Response response) throws Exception {
// TODO 2: Right now, we only serialize the first soup, let's make it so you can choose which
// one you want!
// Get Query parameters, can be used to make your search more specific
String soupname = request.queryParams("soupName");
String soupName = request.queryParams("soupName");
// Initialize a map for our informative response.
Map<String, Object> responseMap = new HashMap<>();
// Iterate through the soups in the menu and return the first one
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/edu/brown/cs/student/main/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ public static void main(String[] args) {

// Setting up the handler for the GET /order and /activity endpoints
Spark.get("order", new OrderHandler(menu));
Spark.get("activity", new ActivityHandler());
Spark.get("show", new TVShowHandler());

Spark.init();
Spark.awaitInitialization();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package edu.brown.cs.student.main.server;

import edu.brown.cs.student.main.activity.Activity;
import edu.brown.cs.student.main.activity.ActivityAPIUtilities;
import edu.brown.cs.student.main.television.TVShow;
import edu.brown.cs.student.main.television.TVShowAPIUtilities;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
Expand All @@ -19,10 +20,9 @@
* This class is used to illustrate how to build and send a GET request then prints the response. It
* will also demonstrate a simple Moshi deserialization from online data.
*/
// TODO 1: Check out this Handler. How can we make it only get activities based on specific keys?
// See Documentation here: https://bored-api.appbrewery.com
// TODO 1.1: Check out this Handler. The current logic will return the top result of searching "squid".

public class ActivityHandler implements Route {
public class TVShowHandler implements Route {
/**
* This handle method needs to be filled by any class implementing Route. When the path set in
* edu.brown.cs.examples.moshiExample.server.Server gets accessed, it will fire the handle method.
Expand All @@ -40,22 +40,23 @@ public Object handle(Request request, Response response) {
// to be fulfilled.
// If you specify a queryParam, you can access it by appending ?parameterName=name to the
// endpoint
// ex. http://localhost:3232/activity?key=num
// ex. http://localhost:3232/show?keyword=squid
Set<String> params = request.queryParams();
// System.out.println(params);
String key = request.queryParams("key");
// System.out.println(key);
String keyword = request.queryParams("keyword");
// System.out.println(keyword);

// Creates a hashmap to store the results of the request
Map<String, Object> responseMap = new HashMap<>();
try {
// Sends a request to the API and receives JSON back
String activityJson = this.sendRequest();
// Deserializes JSON into an Activity
Activity activity = ActivityAPIUtilities.deserializeActivity(activityJson);
// Adds results to the responseMap
String showJson = this.sendRequest(keyword);
// Deserializes JSON into an TVShow
TVShow show = TVShowAPIUtilities.deserializeTVShow(showJson);
// Adds the data we care about to the responseMap
responseMap.put("result", "success");
responseMap.put("activity", activity);
responseMap.put("name", show.name);
responseMap.put("summary", show.summary);
return responseMap;
} catch (Exception e) {
e.printStackTrace();
Expand All @@ -67,28 +68,30 @@ public Object handle(Request request, Response response) {
return responseMap;
}

private String sendRequest() throws URISyntaxException, IOException, InterruptedException {
// Build a request to this BoredAPI. Try out this link in your browser, what do you see?
// TODO 1: Looking at the documentation, how can we modify the URI to query based on specific activity keys?
// HINT: you will want to replace random with a different endpoint!
// TODO 1.1: complete the TODO in Activity.java
HttpRequest buildBoredApiRequest =
private String sendRequest(String keyword) throws URISyntaxException, IOException, InterruptedException {
// Build a request to this TVMaze API. Try out this link in your browser, what do you see?
// TODO 1.1: Looking at the documentation, how can we modify the URI to search for shows based on a specific keyword that we specify?
// See Documentation here: https://www.tvmaze.com/api
String uri = "https://api.tvmaze.com/search/shows?q=" + keyword;

// TODO 1.2: complete the TODO in TVShow.java
HttpRequest buildTVShowApiRequest =
HttpRequest.newBuilder()
.uri(new URI("https://bored-api.appbrewery.com/random"))
.uri(new URI(uri))
.GET()
.build();

// Send that API request then store the response in this variable. Note the generic type.
HttpResponse<String> sentBoredApiResponse =
HttpResponse<String> sentTVShowApiResponse =
HttpClient.newBuilder()
.build()
.send(buildBoredApiRequest, HttpResponse.BodyHandlers.ofString());
.send(buildTVShowApiRequest, HttpResponse.BodyHandlers.ofString());

// What's the difference between these two lines? Why do we return the body? What is useful from
// the raw response (hint: how can we use the status of response)?
System.out.println(sentBoredApiResponse);
System.out.println(sentBoredApiResponse.body());
System.out.println(sentTVShowApiResponse);
System.out.println(sentTVShowApiResponse.body());

return sentBoredApiResponse.body();
return sentTVShowApiResponse.body();
}
}
24 changes: 24 additions & 0 deletions src/main/java/edu/brown/cs/student/main/television/TVShow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package edu.brown.cs.student.main.television;

/**
* This is a class that models an Activity received from the BoredAPI. It doesn't have a lot but
* there are a few fields that you could filter on if you wanted!
*/
// Main TVShow class
public class TVShow {

// Using Postman, examine the response JSon. When looking at the raw response, notice that there are many additional fields beyond name.
// Moshi allows us to pick just the fields we want to examine from our JSON and omit the rest.

// TODO 1.2: Include a summary of the TVShow in addition to the name. Update toString() to display this summary.

public String name;
public String summary;

public TVShow() {}

@Override
public String toString() {
return "The TV Show is " + this.name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package edu.brown.cs.student.main.television;

import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;

/**
* This class shows a possible implementation of deserializing JSON from the TVMaze API into an
* TVShow.
*/
public class TVShowAPIUtilities {

/**
* Deserializes JSON from the TVMaze into an TVShow object.
*
* @param jsonTVShow
* @return
*/
public static TVShow deserializeTVShow(String jsonTVShow) {
try {
// Initializes Moshi
Moshi moshi = new Moshi.Builder().build();

//Looking at the JSON response, we notice that the structure is a JSON array. In order to parse with moshi,
//we need to create a new parameterized type to deal with this list format. This is not necessary when the
//response is not a list of JSONs.
Type listType = Types.newParameterizedType(List.class, TVShowResponse.class);

// Create an adapter for the list of TVShowResponse
JsonAdapter<List<TVShowResponse>> adapter = moshi.adapter(listType);

// Parse the JSON into a list of TVShowResponse objects
List<TVShowResponse> showResponses = adapter.fromJson(jsonTVShow);

TVShow show = showResponses.get(0).show;
return show;
}
// Returns an empty TVShow... Probably not the best handling of this error case...
// Notice an alternative error throwing case to the one done in OrderHandler. This catches
// the error instead of pushing it up.
catch (IOException e) {
e.printStackTrace();
return new TVShow();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package edu.brown.cs.student.main.television;
import com.squareup.moshi.Json;

public class TVShowResponse {
public TVShow show;

public TVShowResponse() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.squareup.moshi.Moshi;
import edu.brown.cs.student.main.server.ActivityHandler;
import edu.brown.cs.student.main.server.OrderHandler;
import edu.brown.cs.student.main.server.TVShowHandler;
import edu.brown.cs.student.main.soup.Soup;
import java.io.IOException;
import java.net.HttpURLConnection;
Expand Down Expand Up @@ -77,7 +77,7 @@ public void setup() {

// In fact, restart the entire Spark server for every test!
Spark.get("order", new OrderHandler(menu));
Spark.get("activity", new ActivityHandler());
Spark.get("show", new TVShowHandler());
Spark.init();
Spark.awaitInitialization(); // don't continue until the server is listening
}
Expand Down