I want to implement classes in a project that will support the loading of configuration data from a JSON file. The JSON file is in the following form. Please be as critical as you can be. I'm a junior developer and I want to learn how to write clean code.
[
{
"teamIdentity": "team01",
"projectIdentities": [
{
"projectIdentity": "project01"
},
{
"projectIdentity": "project02"
},
{
"projectIdentity": "project03"
}
]
},
{
"teamIdentity": "team02",
"projectIdentities": [
{
"projectIdentity": "project04"
},
{
"projectIdentity": "project05"
},
{
"projectIdentity": "project06"
}
]
},
{
"teamIdentity": "team03",
"projectIdentities": [
{
"projectIdentity": "project07"
},
{
"projectIdentity": "project08"
},
{
"projectIdentity": "project09"
}
]
}
]
Here is the unit test written in ScalaTest:
class JsonConfigTest extends FlatSpec {
"JsonConfig" must "load the default JSON file" in {
val config = JsonConfigFactory.newInstance
assert(config != null)
}
it must "throw an exception when the JSON file does not exist" in {
assertThrows[IllegalArgumentException] {
JsonConfigFactory.newInstance("absent-file.json")
}
}
it must "successfully validate the JSON file" in {
pending
}
it must "throw an exception if validation for the JSON file fails" in {
pending
}
it must "return a collection of TeamData objects" in {
pending
}
}
This is the interface for Config
/**
* The interface for configuration.
*/
public interface Config {
/**
* Returns the configuration data for all teams.
*
* @return {@code List<TeamData>}
*/
List<TeamData> getAllTeamData();
}
I then proceed to write the JsonConfigFactory
class.
/**
* The factory for JsonConfig.
*/
public final class JsonConfigFactory {
/**
* Creates a default instance of JsonConfig.
*
* @return {@code JsonConfig}
*/
public static JsonConfig newInstance() {
return new JsonConfig();
}
/**
* Creates a instance of JsonConfg with the filename provided.
*
* @param filename The name of the file that contains the configuration data.
* @return {@code JsonConfig}
*/
public static JsonConfig newInstance(String filename) {
return new JsonConfig(filename);
}
}
This is the concrete implementation of Config
called JsonConfig
that will support the loading of the JSON file.
/**
* The JSON configuration.
*/
public final class JsonConfig implements Config {
/**
* The root JSON node.
*/
private JsonNode rootConfigNode;
/**
* Default constructor.
*/
public JsonConfig() {
this.rootConfigNode = getRootConfigNode(JsonConfigUtils.JSON_CONFIG_FILE_NAME);
}
/**
* Constructor that accepts a filename that contains the configuration data.
*
* @param filename The filename.
*/
public JsonConfig(String filename) {
this.rootConfigNode = getRootConfigNode(filename);
}
@Override
public List<TeamData> getAllTeamData() {
var teamData = new ArrayList<TeamData>();
final var teamConfigNodes = rootConfigNode.elements();
teamConfigNodes.forEachRemaining(node -> teamData.add(getTeamDataFromConfigNode(node)));
return teamData;
}
/**
* Returns the TeamData from a JsonNode.
*
* @param teamConfigNode The node containing the team data.
* @return {@code TeamData}
*/
private TeamData getTeamDataFromConfigNode(JsonNode teamConfigNode) {
return JsonTeamDataFactory.newInstance(teamConfigNode);
}
/**
* Returns the root json node from the file.
*
* @param filename The name of the file.
* @return {@code JsonNode}
*/
private JsonNode getRootConfigNode(String filename) {
try {
final var inputStream = getClass().getClassLoader().getResourceAsStream(filename);
return new ObjectMapper().readValue(inputStream, JsonNode.class);
} catch (IOException e) {
throw new IllegalArgumentException();
}
}
}
This is the helper class for common strings
/**
* The JSON configuration utils.
*/
final class JsonConfigUtils {
/**
* The name of the default JSON configuration file.
*/
static final String JSON_CONFIG_FILE_NAME = "config.json";
/**
* The name of the team identity field.
*/
static final String TEAM_IDENTITY_FIELD = "teamIdentity";
/**
* The name of the project identities field.
*/
static final String PROJECT_IDENTITIES_FIELD = "projectIdentities";
/**
* The name of the team project identity field.
*/
static final String PROJECT_IDENTITY_FIELD = "projectIdentity";
}
This is the interface for TeamData
.
/**
* The interface for TeamData.
*/
public interface TeamData {
/**
* Returns the ID of the team.
*
* @return {@code String}
*/
String getTeamIdentity();
/**
* Returns the IDs of the projects.
*
* @return {@code List<String>}
*/
List<String> getProjectIdentities();
}
This is a factory for JsonTeamData
.
/**
* Factory for JsonTeamData.
*/
public class JsonTeamDataFactory {
/**
* Returns a new instance of JsonTeamData.
*
* @param teamDataNode The JSON node.
* @return {@code JsonTeamData}
*/
public static JsonTeamData newInstance(JsonNode teamDataNode) {
return new JsonTeamData(teamDataNode);
}
}
This is the concrete implementation of TeamData
called JsonTeamData
that will support the loading of JSON.
/**
* The JSON team data.
*/
public final class JsonTeamData implements TeamData {
/**
* The JSON node containing the team data.
*/
private JsonNode teamDataNode;
/**
* The team identity.
*/
private String teamIdentity;
/**
* The collection of project identities.
*/
private List<String> projectIdentities;
/**
* Constructor for JsonTeamData.
*
* @param teamDataNode The JSON node.
*/
public JsonTeamData(JsonNode teamDataNode) {
this.teamDataNode = teamDataNode;
initTeamIdentity();
initProjectIdentities();
}
@Override
public String getTeamIdentity() {
return teamIdentity;
}
@Override
public List<String> getProjectIdentities() {
return projectIdentities;
}
/**
* Initializes the team identity.
*/
private void initTeamIdentity() {
this.teamIdentity = teamDataNode.get(JsonConfigUtils.TEAM_IDENTITY_FIELD).asText();
}
/**
* Initializes the project identities.
*/
private void initProjectIdentities() {
var projectIds = new ArrayList<String>();
final var projectIdsNode = teamDataNode.findValues(JsonConfigUtils.PROJECT_IDENTITIES_FIELD);
projectIdsNode.forEach(node -> {
final var projectIdentityNodes = node.elements();
projectIdentityNodes.forEachRemaining(projectIdentity -> {
projectIds.add(projectIdentity.get(JsonConfigUtils.PROJECT_IDENTITY_FIELD).asText());
});
});
this.projectIdentities = projectIds;
}
}
1 Answer 1
Correctly documented code, readable variable names. Use of design patterns (factory pattern) and of interfaces, static variables named in upper case.
This is coded well. I have nothing negative to say at all.