Spring API - POST & persist data
Persist data in a JSON file. Blog post by Ionuț Alixăndroae
October 21, 2021
9 min readSpring API - How to persist data
This blog post is a continuation of the previous one and the presented code uses the functionality implemented there and it is built on top of it.
Click here to go to the first blog post from this Spring API series or access the GitHub repository.
What is a POST request?
A POST request is an HTTP method used to send data to a server or web service in order to create or update a resource.
Usually the data that is being sent is called a payload and can have different types: JSON, XML, raw text, etc.
POST action in Spring API
As demonstrated in the previous blog post, the controller class is mapping the HTTP action (GET or POST), and the data passed through with the help of dedicated annotations, to the model class that holds the logic.
What we want to do is to take a name
property sent via a POST request and create a new Location
object. This is very similar to the logic inside the GET request, with the difference that there the property was coming as a query parameter, not inside a JSON payload via a POST request. After the new object is created, we want to save it as a JSON object inside a new file called locations.json
. And the request will then respond with the whole content inside this file, technically demonstrating how to add and persist data in a file using a POST request.
{
"locations": []
}
A new method marked with the @RestController
annotation is added to the LocationController.java
class. The @RestController
annotation defines the web service's endpoint address and @RequestBody
makes sure that the payload sent together with the request is passed as an argument to the save()
method.
We have defined the argument of type JsonNode
because the payload will be of type JSON and also because it is easier to parse it later and extract the needed property.
We are interested in the name
property from inside the payload, and we can access it with the following code
body.get("name").asText();
Location newLocation = new Location(counter.incrementAndGet(), body.get("name").asText());
At this point, we have a new Locations
object created dynamically based on user input (payload).
Save data in a file
Inside the model folder I created a new Java class named LocationsFile.java
. The purpose of this class is to establish the connection to the locations.json
file and to take the newly created Location
object and append it there.
package com.ionutalixandroae.demorestapi.model;
import java.io.FileReader;
import java.io.FileWriter;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import com.fasterxml.jackson.databind.ObjectMapper;
public class LocationsFile {
private final String filePath;
private final String path = "/path_to/Spring/demo-rest-api/";
public LocationsFile(String filePath) {
this.filePath = String.format("%s%s", this.path, filePath);
}
public JSONObject saveToFile(Location newLocation) {
// Get locations from file
JSONArray locations = this.getLocations();
// Prepare the objects to convert from Java object to string to JSON
ObjectMapper mapper = new ObjectMapper();
JSONParser parser = new JSONParser();
// This object will hold the new and updated file content
JSONObject fileContent = new JSONObject();
try (FileWriter writer = new FileWriter(this.filePath)){
String newLocationAsString = mapper.writeValueAsString(newLocation);
locations.add(parser.parse(newLocationAsString));
fileContent.put("locations", locations);
writer.write(fileContent.toJSONString());
return fileContent;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private JSONArray getLocations() {
try (FileReader reader = new FileReader(this.filePath)) {
JSONParser jsonParser = new JSONParser();
Object fileContent = jsonParser.parse(reader);
JSONObject jsonContent = (JSONObject) fileContent;
return (JSONArray) jsonContent.get("locations");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
The class has two private properties, one is already initialized and the other one gets initialized inside the constructor. A private method called getLocations() is created to read the content of the file first, take all previously saved objects and return them in an array. This is possible using a FileReader
object that opens the JSON file, parses the content until it extracts the needed array of data and then converts it into a native Java JSONArray
object.
The public method called saveToFile() is accepting the Location
object and appends it to the previously returned array, making sure every resource gets saved inside. Then, using a FileWriter
, the entire content is persisted inside locations.json
and then returned as a JSON response.
LocationsFile locationsFile = new LocationsFile("locations.json");
return locationsFile.saveToFile(newLocation);
Spring Framework is capable of taking the array returned by the saveToFile()
method and translate it into a JSON response for the user.
For every new request, a new object is created and saved inside the locations
array in the locations.json
file.
{
"locations": [
{
"locationInfo": {
"extent": {
"ymin": 47.99941000000007,
"xmin": 11.440540000000055,
"ymax": 48.27341000000007,
"xmax": 11.714540000000056
},
"feature": {
"geometry": {
"x": 11.577540000000056,
"y": 48.13641000000007
},
"attributes": {
"Score": 100,
"Addr_Type": "Locality"
}
},
"name": "Munich, Bavaria"
},
"name": "Munich",
"coordinates": {
"latidude": 48.13641000000007,
"longitude": 11.577540000000056
},
"id": 1
},
{
"locationInfo": {
"extent": {
"ymin": 52.26304000000003,
"xmin": 13.123910000000066,
"ymax": 52.76904000000003,
"xmax": 13.629910000000066
},
"feature": {
"geometry": {
"x": 13.376910000000066,
"y": 52.51604000000003
},
"attributes": {
"Score": 100,
"Addr_Type": "Locality"
}
},
"name": "Berlin"
},
"name": "Berlin",
"coordinates": {
"latidude": 52.51604000000003,
"longitude": 13.376910000000066
},
"id": 2
}
]
}
Conclusions
Implementing a new web service to handle POST actions is extremely easy with a few lines of code. Similarly, with the GET requests, Spring performs the hard work of mapping all data and properties and, by using the model-controller pattern, one can effortlessly create complex logic for data transactions and data persistence.
I used a file here for all data reading and writing operations, but this can be simply changed with a database, and then the web service will be better prepared for multi-user concurrent requests 💪
I would like to continue this series by approaching and implementing all CRUD operations so that the final code will be able to serve as a ready-to-use boilerplate for future use.
Up to date codebase can be found in the repository here.