ByteIntroduction

Getting started with Jackson

Skills:

Java

Objective

Get a quick overview of serialization & deserialization of JSON using Jackson

Background/Recap


What is JSON?


How will a Python application communicate with a SpringBoot server? They need a common language to convey information, right? For this purpose there are standard formats like JSON, XML etc, made use of for transferring data across systems.


JSON is a readable format with key-value pairs


{

    "symbol": "AAPL",

    "quantity": 100,

    "purchaseDate": "2019-01-02"

}

The keys have to be a String whereas values comes from a much wider pool of objects like String, Integer, Boolean, Array, or even another JSON nesting


Converting JSON to a Java object for processing is deserialization whereas serialization involves transforming the Java object to JSON.

Primary goals

  1. Understand how Jackson makes serialization/deserialization easier

  2. Understand the role object variable name plays

  3. Understand some of the basic annotations to configure Jackson

  4. Understand relation between setter/getter methods & Jackson

Objective

Get a quick overview of serialization & deserialization of JSON using Jackson

Background/Recap


What is JSON?


How will a Python application communicate with a SpringBoot server? They need a common language to convey information, right? For this purpose there are standard formats like JSON, XML etc, made use of for transferring data across systems.


JSON is a readable format with key-value pairs


{

    "symbol": "AAPL",

    "quantity": 100,

    "purchaseDate": "2019-01-02"

}

The keys have to be a String whereas values comes from a much wider pool of objects like String, Integer, Boolean, Array, or even another JSON nesting


Converting JSON to a Java object for processing is deserialization whereas serialization involves transforming the Java object to JSON.

Primary goals

  1. Understand how Jackson makes serialization/deserialization easier

  2. Understand the role object variable name plays

  3. Understand some of the basic annotations to configure Jackson

  4. Understand relation between setter/getter methods & Jackson

Download & run source code

  • Create ~/workspace/bytes/ directory and cd to it

mkdir -p ~/workspace/bytes

cd ~/workspace/bytes

  • Download the source code to the ~/workspace/bytes directory from here using one of the following commands:

git clone https://gitlab.crio.do/crio_bytes/jackson.git

git clone git@gitlab.crio.do:crio_bytes/jackson.git

  • Verify by running Main.java, you’ll see Running Completed outputted in the terminal

How would you approach reading data from a JSON file?

Your friend has a list of stocks she’d purchased, in the JSON format.


[

  {

    "symbol": "AAPL",

    "purchaseDate": "2019-01-02"

    "quantity": 100

  },

  {

   "quantity": 10,

   "purchaseDate": "2019-01-02",

    "symbol": "MSFT"

  }

]

You being someone who deals with computer stuff is asked to parse it, store each stock data as an object and print out the contents in a specific format. Try to jot down the steps you’ll have to follow to do this. If you’re curious enough, jump in to write the code itself, inside the parseJsonManually() method. You can store the data as objects of the Trade class provided. The output has to be like this (the ordering is to be followed)


purchaseDate=2019-01-02, quantity=100, symbol=AAPL

purchaseDate=2019-01-02, quantity=10, symbol=MSFT

How did it go? Takes time, but doable? How about this one?


Whether you wrote down the logic or coded it, you would’ve noticed that the key names have to be hardcoded, spaces have to be ignored, not everything is inside quotes so you can’t go on vaguely finding quotes and getting stuff inside it.

Reading JSON Jacksomatically

We’ll now try to do the same utilizing Jackson. This does the work for us


ObjectMapper objectMapper = new ObjectMapper();

Trade[] trades = objectMapper.readValue(file, Trade[].class);

for (Trade trade : trades) {

      System.out.println(trade);

}

Ok, where’s the rest of the code? Feel free to put extra spaces but this is all we need to read contents of the JSON file, store them as Trade objects and print out the values as required. The readValue method takes in the File object for our JSON file as well as the class to which we need to Object Map the JSON contents to. We’ll read each stock data as a Trade object to an Array, which is why the second parameter is Trade[].class


Run the Main class, you’ll be able to see the deserialized output in the terminal

image alt text

People always want more. With being able to see the stock data printed prettily, your friend now wants a list of stocks with more than 50 shares, back into a JSON file. Jackson ObjectMapper provides a writeValue() method to write Java objects back to JSON (serialization). The arguments are the deserialized data & the File object to write it to. Fill the arguments for writeValue() to complete the writeImportantPurchasesToFile() method. Verify the content of the JSON file created in impTrades.json


[{"symbol":"AAPL","quantity":100,"purchaseDate":"2019-01-02"}]

Curious cats

  • Can you parse the purchaseDate field as any date object Java provides rather than a String? (Ref)

How does Jackson know which variable to map a JSON key to?

If you noticed how we were filtering the stock data earlier, the quantity variable was called to fetch the number of shares. How did Jackson know which variable in Trade class to map a particular key’s value to? Does something stand out on comparing the Trade class & the JSON data?


public class Trade {

  

  private String symbol;

  private int quantity;

  private String purchaseDate;

  

  ....

}


[

  {

    "symbol": "AAPL",

    "quantity": 100,

    "purchaseDate": "2019-01-02"

  },

  {

    "symbol": "MSFT",

    "quantity": 10,

    "purchaseDate": "2019-01-02"

  }

]

Hmm, looks like the variable names are the same as the key names. Not convinced? Change the variable names & see what Jackson tells you then (via the stack trace in error)

Curious cats

  • Isn’t the empty constructor in Trade class simply taking up space, do we need that? Or is it the other way around :) (Hint: Put a breakpoint on one of the statements in the constructor with arguments. For empty constructor, add breakpoint on a dummy statement like "”.isEmpty())

  • What will Jackson do if there are duplicate keys? When would you not want the default behaviour to happen? How would you go on to override the behaviour? Technically speaking, are duplicate keys in JSON to be dealt by the JSON creator or the user? (Hint: RFC)

How to customize default Jackson behaviour?

We will need to configure how Jackson works according to our needs. Let’s take a case. How would you write your Trade class if the JSON was like this?


[

  {

    "1. symbol": "AAPL",

    "2. quantity": 100,

    "3. purchaseDate": "2019-01-02",

  },

  {

    "1. symbol": "MSFT",

    "2. quantity": 10,

    "3. purchaseDate": "2019-01-02",

  }


Oh, oh! How would you name a variable 1. Symbol? Or will we get lucky with Jackson doing some fancy stuff to understand our requirement? Run the parseJsonJacksomatically() method with the tradesFancy file as parameter.


You’ll get an error, try to understand what it’s trying to let us know. This is where Jackson annotations come into picture. We can add the @JsonProperty annotator on top of the variable name for mapping a key name to it. Like this

image alt text

Add similar annotations to the other variables and ensure you are able to run the program without errors. Answer first question at the page end.


Now, the newer JSON versions include an additional field of no value to us.


{

    "1. symbol": "AAPL",

    "2. quantity": 100,

    "3. purchaseDate": "2019-01-02",

    "4. rainyDay": true

}

Run parseJsonJacksomatically() with the tradesFancier file as input to it. You got an error, right? What’s it saying?


So, to parse any JSON file by default, Jackson needs a mapping for all of the keys in it. Our Trade class doesn’t have a fourth variable. There are two ways to solve this:

  1. We add a new variable and make use of an annotation to map the field 4. rainyDay to it

  2. We know which all fields we need & hence decide not to include any latecomers to the party aka, learn about a new annotation to ignore any unknown fields.


I’ll give you a couple of seconds to make your mind. 1 or 2… 1 or 2… 1 or 2…


It’s the second option, right? Awesome!


@JsonIgnoreProperties annotation is the one to seek help here. The variables were crowned the @JsonProperty annotation earlier. This one is a class level annotation meaning that you’ll have to place it on top of the Trade class. Do a quick Google search on how to use the annotation, implement it & see if the error vanishes now.

Curious cats

  1. You have a database of user information including username, profile pic link and password. When the user visits his profile, the database is queried and returns a User object with these 3 values. We don’t want the password to be included when we serialize the object for sending to the user. How would you do that?

Jackson & Getters/Setters

All variables in the Trade class were public. We might need to restrict access to these in some cases. You are given another TradePrivate class with private variables. Have a glance at it and run the parseJsonJacksomaticallyPrivate() method with the trades file. Getting any errors?


Jackson by default doesn’t see variables with non-public access modifiers. We’ll have to provide getter & setter methods in the TradePrivate class for that. This is how we add getter & setter for the symbol variable


public String getSymbol() {

    return symbol;

}


public void setSymbol(String symbol) {

    this.symbol = symbol;

}

Add getters & setters for other variables similarly. The parseJsonJacksomaticallyPrivate() method will run now.

How would I know Jackson is using these methods to serialize & deserialize?

We can use the debugger to check the program flow. Put a breakpoint inside any of the setter methods and run the Main class on debug mode. Verify themain() method is calling


main.parseJsonJacksomaticallyPrivate(tradesFancier, outputFile);

And the parseJsonJacksomaticallyPrivate() method has only the deserialization call (objectMapper.readValue())


Did it stop? What do you think happened?

image alt text

So, Jackson uses the setter methods to deserialize JSON for non-public variables.


Now you know why defining setters makes the non-public variables deserializable.

Curious Cats

  • You noticed the setter being called when deserializing the JSON string into a Java object. What do you think will get called when serialization happens? Can you prove that by setting an appropriate breakpoint in the TradePrivate class?

  • Can you find any annotation to make non-public fields serializable without adding getters?

  • Add only a getter for a private variable and it should only be serializable, right? Can you try out if it can be deserialized for me? Is there any anomaly? Do feel free to see if the same happens with only a setter

  • See if there’s any difference in the serialized output if we set the getter as getSymbol & getSYmbol? Try out different variations as well

Takeaways

  • Why do we need a library to perform serialization/deserialization?

  • How does Jackson understand which variable to map a JSON key to?

  • Now you know what annotations do and how basic Jackson annotations like @JsonProperty & @JsonIgnoreProperties fit in

  • What do serialization and deserialization processes use - getters/setters?

Curious cats

  • Ready for more? Fetch Google’s stock data from the API endpoint provided below. Write code to parse it. You can get an API token here

https://api.tiingo.com/tiingo/daily/AAPL/prices?startDate=2019-01-02&endDate=2019-12-12&token=<your-api-token>