Java-Web:Exercises - Introduction to JSON

From Juneday education
Jump to: navigation, search

Work in progress

This chapter is a work in progress. Remove this section when the page is production-ready.

Introduction

This chapter contains exercises for the introduction to JSON chapter. There isn't much to do with plain JSON so the exercises move straight on to parsing JSON from Java.

If your application uses JSON as a data transmission format (reading JSON files from the file system or the internet), you will need to know the basics for how to parse (read and create a Java data type object) JSON documents. We'll use the standard Java API from javax.json as well as the org.json API (which is used among other places in Android).

With the Java API, you get the javax.json package, which contains interfaces and classes for handling JSON.

With the org.json you get a different set of interfaces and classes documented here.

Exercise - parse a JSON file and print out the contents

The purpose of this exercise is to give you the basic tools and skills needed to receive and use an JSON document in a Java application. There are many APIs for using JSON around but we are sticking to standard Java using the API in the javax.json package, and the org.json package (this is the one used in for instance Android).

You will have use for the stuff you learn here, even if you will use a third party API later on for parsing XML. The basic idea is very similar and built around the document object model (DOM) described by W3C.

Task 1 - Download the JSON files to parse

As usual, create a fresh directory for the exercise and cd to that directory.

Download duke.json from our github repo.

Download students.json.

You will use duke.json for the first part of the exercise with javax.json and students.json for the second part of the exercise with org.json.

Task 2 - Part 1 - javax.json - Write a stand-alone parser using javax.json

The purpose of this part is to create a small parser for the duke.json file, seen in the lecture slides. We'll create it as a small self-contained Java program, since this exercise is just to get you familiar with the API in javax.json, and not an exercise in writing large object oriented systems with great architecture.

Normally, you wouldn't write a small application in Java like this, so please note that this exercise is greatly simplified to keep it small and focused on javax.json.

Start by getting the JAR file with javax.mail. Put it in the current directory, or in a sub directory called lib or similar. The important thing is that you know where it is, since you'll need it in your class path both for compiling and for running the small application.

You can get the files from either one of these places:

We'll call this Java application JavaxMain (in a file called JavaxMain.java so that we can tell it from the other application we'll write in part 2.

Import the following packages in your source file:

import java.io.*;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonValue;
import javax.json.JsonObject;
import static javax.json.JsonValue.ValueType.*;
import javax.json.JsonReader;
import javax.json.JsonStructure;

Write the main method and in it, one single try-catch block catching FileNotFoundException and if so, print an error message to system out using the message from the exception you caught.

In the try-block, make a call to a static method in the same class (not written yet) named parseAndPrint passing along args[0] as the single argument.

Write a static void method called parseAndPrint (called from main) which is declared as throws FileNotFoundException.

This is actually enough for the first compilation round. If you want to avoid the compiler error about FileNotFoundException not being thrown from the method, add a line throwing a FileNotFoundException in the method.

Compile the class with the JAR file on the class path.

If it doesn't complain about your imports, then you are set to continue. If it complains, there is something wrong with your class path or the jar file.

Expand using link to the right to see a hint.

import java.io.*;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonValue;
import javax.json.JsonObject;
import static javax.json.JsonValue.ValueType.*;
import javax.json.JsonReader;
import javax.json.JsonStructure;

public class JavaxMain{
  public static void main(String[] args){
    try{
      parseAndPrint(args[0]);
    }catch(FileNotFoundException fnfe){
      System.err.println(fnfe.getMessage());
    }
  }
  static void parseAndPrint(String jsonFile) throws FileNotFoundException{
    throw new FileNotFoundException(); // remove this line in the next task!
  }
}
$ javac -cp .:javax.json.jar JavaxMain.java && java -cp .:javax.json.jar JavaxMain

Note that on Cygwin/Windows, you'll need to use the Windows syntax for the class path: -cp ".;javax.json.jar", since Windows uses ; between the parts in the class path, while Mac OS and GNU/Linux use :.

Please remember that you have to include the JAR file in the class path both when compiling and when running. Why? Because your application is using classes which Java can't find "out-of-the-box". The javax... packages for JSON is not included in the standard Java API which comes with your installation.

Task 3 - Part 1 - javax.json - Add code for the parseAndPrint method

In the parseAndPrint() method, add the following steps. We'll assume that you called the parameter String jsonFile.

Add a try-catch block for FileNotFoundException which in the catch clause simply re-throws the exception unchanged. There isn't anything this method can do about the case of a missing json file. Why not just let the exception be thrown then, why bother with a try-catch? You might change your mind later and throw something else (like a runtime exception) in the catch clause, so you'll have the structure in place.

In the try-block, create a JsonReader reference called reader, initialized to the result of calling Json.createReader() giving the jsonFile reference as the single argument to the method.

Create a JsonStructure reference called jsonStruct, initialized to the result of calling reader.read() (without arguments).

Call close() on the reader.

If you want to make sure you were able to read the JSON file, you can print the contents of jsonStruct like this:

      System.out.println(jsonStruct);

Write an if-statement checking whether calling getValueType() on jsonStruct equals OBJECT. You have access to the name OBJECT because of this import statement:

import static javax.json.JsonValue.ValueType.*;

In the if-block (the code to execute if the condition evaluates to true, that is, it was of type OBJECT), create a JsonObject reference called jo intialized to the result of casting jsonStruct to type JsonObject.

Print out the following values from the JsonObject:

  • firstName
  • lastName
  • age
  • streetAddress
  • city
  • state
  • postalCode

For everything, except age, use the method getString() on the jo JsonObject reference. Read about it here. Note that there's an overloaded version of this method which takes two arguments, where the second argument is a default value to use if the name is not found. Use the overloaded version for the fields streetAddress, city, state, and, postalCode.

Using default values like this, means that the JSON file can be considered valid even if the above listed names don't exist in it. Since we are using the toString() with only one argument (no default value) for the names firstName, lastName, and, age, that could be thought of as meaning that those names (and values) are mandatory.

We've now printed the following part of the JSON file:

{
   "firstName": "Duke",
   "lastName": "Java",
   "age": 18,
   "streetAddress": "100 Internet Dr",
   "city": "JavaTown",
   "state": "JA",
   "postalCode": "12345",
   "phoneNumbers": [
      { "Mobile": "111-111-1111" },
      { "Home": "222-222-2222" }
   ]
}

Now, we'll get the array of phone numbers.

Create a JsonArray reference called arr initialized to the result of calling getJsonArray("phoneNumbers") on the jo reference.

Use a for-each loop with loop variable JsonValue jv looping over arr.


In the loop body, use an if-statement to check if ((JsonObject)jv).keySet().contains("Mobile"). In the if-body, create a String mobile intialized to ((JsonObject)jv).getString("Mobile"). If the string isn't null, then print the value to standard out.

In the else-clause to the first if-statement, do the same for "Home". Now, you have conditionally printed home and mobile phone numbers if they existed. This is an alternative to using the version with default values.

Expand using link to the right to see a hint.

  public static void parseAndPrint(String jsonFile)throws FileNotFoundException{
    try{
      JsonReader reader = Json.createReader( new FileReader(jsonFile) );
      JsonStructure jsonStruct = reader.read();
      reader.close();
      if(jsonStruct.getValueType().equals(OBJECT)){
        JsonObject jo = (JsonObject) jsonStruct;
        System.out.println("First name: " + jo.getString("firstName"));
        System.out.println("Last name: " + jo.getString("lastName"));
        System.out.println("Age: " + jo.getInt("age"));
        System.out.println("Street address: " + jo.getString("streetAddress", "No st. addr."));
        System.out.println("City: " + jo.getString("city", "No city"));
        System.out.println("State: " + jo.getString("state", "No state"));
        System.out.println("Postal code: " + jo.getString("postalCode", "No postal code"));
        JsonArray arr = jo.getJsonArray("phoneNumbers");
        System.out.println("Phone numbers:");
        for(JsonValue jv : arr){
          /* JsonObject is a Map - check if it has the correct key */
          if( ((JsonObject)jv).keySet().contains("Mobile")){
            String mobile = ((JsonObject)jv).getString("Mobile");
            if(mobile!=null){
              System.out.println(" Mobile: " + mobile);
            }
          }else if(((JsonObject)jv).keySet().contains("Home")){
            String home   = ((JsonObject)jv).getString("Home");
            if(home!=null){
              System.out.println(" Home: " + home);
            }
          }
        }
      }
    }catch(FileNotFoundException fnfe){
      // You could do some logging before re-throwing the exception
      // You could also remove the throws clause of this method and
      // throw a runtime exception instead - but then main should
      // have a handler which catches that exception instead.
      throw new FileNotFoundException("jsonFile");
    }

Alternative version using try-with-resources:

  public static void parseAndPrint(String jsonFile)throws FileNotFoundException{
    try(JsonReader reader = Json.createReader( new FileReader(jsonFile) );){      
      JsonStructure jsonStruct = reader.read();
      if(jsonStruct.getValueType().equals(OBJECT)){
        JsonObject jo = (JsonObject) jsonStruct;
        System.out.println("First name: " + jo.getString("firstName"));
        System.out.println("Last name: " + jo.getString("lastName"));
        System.out.println("Age: " + jo.getInt("age"));
        System.out.println("Street address: " + jo.getString("streetAddress", "No st. addr."));
        System.out.println("City: " + jo.getString("city", "No city"));
        System.out.println("State: " + jo.getString("state", "No state"));
        System.out.println("Postal code: " + jo.getString("postalCode", "No postal code"));
        JsonArray arr = jo.getJsonArray("phoneNumbers");
        System.out.println("Phone numbers:");
        for(JsonValue jv : arr){
          /* JsonObject is a Map - check if it has the correct key */
          if( ((JsonObject)jv).keySet().contains("Mobile")){
            String mobile = ((JsonObject)jv).getString("Mobile");
            if(mobile!=null){
              System.out.println(" Mobile: " + mobile);
            }
          }else if(((JsonObject)jv).keySet().contains("Home")){
            String home   = ((JsonObject)jv).getString("Home");
            if(home!=null){
              System.out.println(" Home: " + home);
            }
          }
        }
      }
    }catch(FileNotFoundException fnfe){
      throw new FileNotFoundException("jsonFile");
    }
  }

Compile and run the application, giving it the argument duke.json (one of the two json-files you downloaded above).

Expand using link to the right to see a hint.

$ javac -cp .:javax.json.jar JavaxMain.java && java -cp .:javax.json.jar JavaxMain duke.json

Verify that you get output similar to:

First name: Duke
Last name: Java
Age: 18
Street address: 100 Internet Dr
City: JavaTown
State: JA
Postal code: 12345
Phone numbers:
 Mobile: 111-111-1111
 Home: 222-222-2222

You may laborate with removing some elements from the JSON file to see if the default values work.

That's the end of the small exercise for part 1 - using javax.json.

Task 4 - Part 2 - org.json - Create a small parser using the org.json API

Now, we'll do the same thing but we'll use students.json as the input JSON file, and we'll use another API, org.json.

The JSON file students.json should look like this:

{
    "students":[
        {
            "studentName":"Anders Andersson",
            "studentID":1
        },
        {
            "studentName":"Beata Bengtsson",
            "studentID":2
        },
        {
            "studentName":"Charlie Christensen",
            "studentID":3
        },
        {
            "studentName":"Dick Dale",
            "studentID":4
        },
        {
            "studentName":"Edward Eriksson",
            "studentID":5
        }
    ]
}

Create a small Java class with a main method. Call the class OrgJsonMain.

In the main method, start by creating a try-catch block catching both IOException and JSONException. Print something to standard error in the respective catch blocks (the former is about reading the file, the latter is about parsing the JSON inside the file).

You'll need the following import statements:

import org.json.*;
import java.io.*;

In the try-block of the main method, create a String reference called json and initialize it to the result of calling a static String method in the same class (not created yet!) with the following signature and throws declaration getJsonAsString(String file) throws IOException. Give args[0] as the single argument to the method, when you call it from main.

Write the method getJsonAsString(String file) throws IOException (it should be of return type String and be a static method.

In the block, for now, just create a StringBuilder called sb as a new StringBuilder. Return sb.toString().

Next compile, using android-json.jar in the class path.

If you want to avoid the errors about exceptions not being thrown, you can throw some phony exception which you will remove in the next step. The point of compiling now is to verify that importing org.json.* works (which depends on your putting the JAR file on the class path).

Download android-json.jar from here (Apache 2.0 license) - save as android-json.jar

Expand using link to the right to see a hint.

import org.json.*;
import java.io.*;

public class OrgJsonMain{
  public static void main(String[] args){
    try{
      String json = getJsonAsString(args[0]);
      if(true){ throw new JSONException("");}  // remove this line later!
    throw new IOException(); // remove this line later!
    }catch(IOException ioe){
      System.err.println("Couldn't read file: " + ioe.getMessage());
    }catch(JSONException jsone){
      System.err.println("Couldn't parse JSON: " + jsone.getMessage());
    }
  }
  private static String getJsonAsString(String file) throws IOException{
    StringBuilder sb = new StringBuilder();
    return sb.toString();
  }
}

To compile:

$ javac -cp .:android-json.jar OrgJsonMain.java

Task 5 - Part 2 - org.json - Implement the getJsonAsString() method

Start by removing any throw-statements you might have created in the previous step (the ones you maybe added to be able to compile).

Expand using link to the right to see a hint.

import org.json.*;
import java.io.*;

public class Second{
  public static void main(String[] args){
    try{
      String json = getJsonAsString(args[0]); 
   }catch(IOException ioe){
      System.err.println("Couldn't read file: " + ioe.getMessage());
    }catch(JSONException jsone){
      System.err.println("Couldn't parse JSON: " + jsone.getMessage());
    }
  }
  private static String getJsonAsString(String file) throws IOException{
    StringBuilder sb = new StringBuilder();
    return sb.toString();
  }
}

Next, inside the method, add the following steps to read in the students.json file (downloaded earlier).

First, create a local StringBuilder called sb as a new StringBuilder. You'll use this to read in the text from the JSON file.

Create a String called ls and initialize it to System.getProperty("line.separator").

Create a BufferedReader called reader and initialize it to new BufferedReader(new FileReader (file)) - we assume that the parameter String to the method is called file.

Create a String called line.

Now, create a while loop for reading the lines from the file. Use the following for loop condition:

(line = reader.readLine()) != null

In the loop body, append line to sb and then append ls (the line separator).

After the loop, close the reader and return sb.toString().

Expand using link to the right to see a hint.

  private static String getJsonAsString(String file) throws IOException{
    StringBuilder sb = new StringBuilder();
    String        ls = System.getProperty("line.separator");
    BufferedReader reader = new BufferedReader(new FileReader (file));
    String line;
    while((line = reader.readLine()) != null) {
      sb.append(line);
      sb.append(ls);
    }
    reader.close();
    return sb.toString();
  }

Task 6 - Part 2 - org.json - Finish the main method and parse the json string

Now, back to the main method. After the declaration and initialization of the json string, perform the following steps.

Create a JSONTokener called jt and initialize it to a new JSONTokener with json (the string) as the only argument to the constructor.

Create a JSONObject called jo as a new JSONObject with jt as the argument to the constructor.

Create a JSONArray called ja by calling getJSONArray("students") on the jo reference.

Create a for-loop with the following head: for(int i = 0;i<ja.length();i++).

In the loop body, re-assign jo to ja.getJSONObject(i) (get the i:th JSON object from the array you are looping through).

Still in the loop body, print first the value of the name "studentName", then of the name "studentID".

For "studentName", use jo.getString("studentName"), and for "studentID", use jo.getInt("studentID").

Expand using link to the right to see a hint.

      JSONTokener jt = new JSONTokener(json);
      JSONObject jo = new JSONObject(jt);
      JSONArray ja = jo.getJSONArray("students");
      for(int i = 0;i<ja.length();i++){
        jo=ja.getJSONObject(i);
        System.out.println("Name: " + jo.getString("studentName"));
        System.out.println("ID: "   + jo.getInt("studentID"));
      }

Compile the application and run it with students.json as the argument.

Expand using link to the right to see a hint.

$ javac -cp .:android-json.jar OrgJsonMain.java && java -cp .:android-json.jar OrgJsonMain students.json
Name: Anders Andersson
ID: 1
Name: Beata Bengtsson
ID: 2
Name: Charlie Christensen
ID: 3
Name: Dick Dale
ID: 4
Name: Edward Eriksson
ID: 5

If you get the following output, your done!

Name: Anders Andersson
ID: 1
Name: Beata Bengtsson
ID: 2
Name: Charlie Christensen
ID: 3
Name: Dick Dale
ID: 4
Name: Edward Eriksson
ID: 5

Links

External links

Source code (suggested solutions)

Navigation

Up next: Creating JSON!

« PreviousBook TOCNext »