Get a quick overview of serialization & deserialization of JSON using Jackson
Get a quick overview of serialization & deserialization of JSON using Jackson
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.
Understand how Jackson makes serialization/deserialization easier
Understand the role object variable name plays
Understand some of the basic annotations to configure Jackson
Understand relation between setter/getter methods & Jackson
Get a quick overview of serialization & deserialization of JSON using Jackson
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.
Understand how Jackson makes serialization/deserialization easier
Understand the role object variable name plays
Understand some of the basic annotations to configure Jackson
Understand relation between setter/getter methods & Jackson
~/workspace/bytes/
directory and cd
to it
mkdir -p ~/workspace/bytes
cd ~/workspace/bytes
~/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
Main.java
, you’ll see Running Completed outputted in the terminalYour 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.
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
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"}]
purchaseDate
field as any date object Java provides rather than a String
? (Ref)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)
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)
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
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:
We add a new variable and make use of an annotation to map the field 4. rainyDay to it
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.
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.
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?
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.
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
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?
https://api.tiingo.com/tiingo/daily/AAPL/prices?startDate=2019-01-02&endDate=2019-12-12&token=<your-api-token>