JSON in place of property files

When building applications there is often the need to store properties or messages typically based on environment. Traditionally java developers will create a .properties file or store properties within a java class file but as static properties start to resemble objects and attributes there has been an adoption of other file formats. Frameworks such as spring boot has begun to support YAML though it does yet support YAML in @PropertySource. YAML "aims to be human readable data interchange format" which gained its popularity with C, perl and python. JSON closely resembles to YAML which largely has been adopted due to its simplicity and prevalence in javascript development. I was working with a project team to create a navigation component and we stepped back from "what we did yesterday" and used JSON to represent a more advanced static structure, below describes the story.

Navigation in its simplest form can be seen visually by visiting jquery navigation page. You have a NavItem that contains a list of NavItems to represent fly out 1:M relationship.

JqueryUI menu

Requirements

When you add requirements such as filter nodes based on a business rule, apply custom parameters to the link and maintain a flexible structure to add nodes it takes us beyond just having a static JSON or HTML hosted on a CDN that applications consume. There are various options such as pulling items from a database but since these items rarely change it isn't necessary to make another database read. Another approach as described above would to store items in a properties file but since it is complex structure it lead to a static JSON and an associated java structure.

Thinking through this decision. Storing navigation as JSON should allow flexibility when adding a node with no code change, no database request, migration, etc. If went with an approach where we tied all navigation together with an Enum, code would change and the flexibility of adding subnodes (fan out) might be more difficult. In addition, taking advantage of some jackson magic which we will show below, all nodes should get populated recursively down the tree. From a maintenance perspective JSON is a natural readable format so most folks should pick this up fairly quickly. If there is concern with format, ie. missing a comma or curly brace, you could add a unit test to validate and use json online formatters to help.

[
   {
      "key": "lul-java-examples",
      "linkText":"Examples",
      "url":"/java/examples/"
   },
   {
      "key": "lul-java-exercises",
      "linkText":"Exercises",
      "url":"/java/exercises/",
      "items":[
         {
            "key": "lul-java-exercises-beginner",
            "linkText":"Beginner",
            "url":"/java/exercises/beginner/"
         }
      ]
   }
]

The associated java object:

class NavItem {
    private String key;
    private String linkText;
    private String url;
    private List<NavItem> items;
    //...
}

Base navigation

Base navigation can be considered navigation shown to all users. There are several ways to read a properties file and two primary ways to convert json to a java object. Reading in the base we will use jackson to convert a json array to a java arraylist. By default jackson will walk JSON tree recursively setting the java object based on the JSON structure. In combination with the NavItems above we can transform the json to java object.

ObjectMapper objectMapper = new ObjectMapper();

List<NavItem> navigation = objectMapper.readValue(
        jsonString,
        objectMapper.getTypeFactory().constructCollectionType(
                List.class, NavItem.class));

assertEquals(4, navigation.size());

Custom rules

One requirement is to apply a custom rules to the navigation element. For instance, lets say a client wants the link text changes from 'Exercises' to 'My Exercises'. Again, we could throw all these into a DB, do some out joins and bam! - we would have what we need. Since there is a limited number of these rules to save a network hop we threw these into a JSON structure as well. Interesting thing is that for a particular key, you could have multiple nodes you want updated. A very natural data structure is guava multimap where we can transform json to a multimap. By adding jackson-datatype-guava module and the java code snippet below we were able to perform that mapping.

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-guava</artifactId>
    <version>2.2.0</version>
</dependency>
{
   "SOME-KEY":[
      {
         "key":"lul-java-examples",
         "linkText":"My Examples",
         "url":"/java/somenewurl/"
      },
      {
        "key": "lul-java-exercises",
        "linkText":"My Exercises",
        "url":"/java/exercises/",
      }
   ]
}
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new GuavaModule());

Multimap<String, NavItem> navs = objectMapper.readValue(
        objectMapper.treeAsTokens(objectMapper.readTree(jsonString)),
        objectMapper.getTypeFactory().constructMapLikeType(
                Multimap.class, String.class, NavItem.class));

Other thoughts

A few things left out of this post was the glue code to remove or replace elements. Depending on coding standards, you could use recursion, guava filtering mechanisms, or java 8 stream filtering.

Other design decisions considered:

  • Instead of starting with a base of navigation elements, why not build up instead of filtering away.
  • If you are working with JSON, why not use JSONpath and filter custom elements that way
  • Instead of working with a java object, why not just work with JSON nodes and walk the tree

This might be easier using another language but with the constraint of java and environment this was chosen - we will see how it goes! Next time before you settle on using a properties file consider using JSON to store your complex data structures.