Read YAML in Spring Boot

Properties written in YAML format in my opinion reads better, far better for a human, especially when an application has tons of properties in there. Well, after all YAML was designed to be a human-readable data-serialization language. Officially it complies with JSON and is a subset of the latter (depends which version though as I’ve read).

Oh, YAML stands for “YAML Ain’t Markup Language“. It’s silly, right, but yeah.

What’s this about?

I kind of thought of using YAML to hold the paths to cherry-pick JSON fields and their values that I’m interested in. I don’t want a whole huge JSON data. Just the interesting bits. Enough for what I need. As requirements dictates. Also, this is one solution (perhaps not the best), if I do not know the JSON schema beforehand. I cannot create POJOS of what I do not know. However, I can make assumptions of the JSON paths. What I do know is what transformations were to take place, what to do with it, and how it should look like in the end.

In other words, the data source is vague, but the output required is a clearly defined JSON payload object. I can “mock” the JSON data coming in, do some work accordingly, then make it so the output looks like what was specified. You get the picture…

The downside to this is the JSON path approach. It is limited to some extent. This will work quite well, however, for a straight one to one mapping from source to target.

First, I did not want to put these in some Java constants class in a util package.

Why not in the normal application.yml file instead?

One word. Separation. That’s the second reason. I wanted it in a different file all by itself. It can grow as large as I would let it, and the normal application properties file won’t have to get affected.

Now to the how.

This is the class that we need – PropertySource.

Step #1

Spring Boot has support for @PropertySource annotation, that we can point to use a YAML factory. We can define it like this.

package xyz.joseyamut.config.factory;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;
import java.util.Properties;

public class JsonPathPropertySourceFactory implements PropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource)
            throws IOException {
        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
        factory.setResources(resource.getResource());

        Properties properties = factory.getObject();

        return new PropertiesPropertySource(resource.getResource().getFilename(), properties);
    }
}

Step #2

Now that we have that, I can take the next step. Design my YAML file structure. For this example, let’s keep it simple.

I’m going to name this file as json-paths.yml. This is saved in the application resources folder. We all know where that is already, right?

It looks like this.

# JSON paths to fields properties
fields:
  version: 1234567890
  metadata:
    - name: foobar
      path: $.metadata.foobar
    - name: hello-world
      path: $.metadata.hello.world
  errors:
    claims: $.errors.claims[0:]
    resolutions: $.errors.resolutions[0:].offer

Here we have the JSON fields I want to get from a JSON object. The path to that field is written in dot notation way.

Just to be clear, this YAML does not represent the JSON object structure. This is me trying to group things logically so I can effectively use it for my needs and, of course, it is but an example only. Those path values could be different from the actual JSON, and it is easy for me to change it later on.

Step #3

I want Spring Boot to get this file when my application boots up. Then I can just wire it for use. That means I’m gonna create a configuration class for this one.

package xyz.joseyamut.config;

import xyz.joseyamut.config.factory.JsonPathPropertySourceFactory;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.util.List;

@Configuration
@ConfigurationProperties(prefix = "fields")
@PropertySource(value = "classpath:json-paths.yml", factory = JsonPathPropertySourceFactory.class)
@Getter
@Setter
public class JsonPathProperties {

    private Long version;

    private List<Metadata> metadata;

    private Errors errors;

    @Getter
    @Setter
    public static class Record {
        private String name;
        private String path;
    }

    @Getter
    @Setter
    public static class Errors {
        private String claims;
        private String resolutions;
    }

}

So far I have everything that I need as far as getting those properties into the application and using it however I please.

How to use it?

I can now @Autowire this all over the Spring Boot application however I want it.

As simple as:

@Autowired
private JsonPathProperties properties;

Then:

List<Metadata> metadataList = properties.getMetadata();

Will output something like:

[
    {
        "name": "foobar",
        "path": "$.metadata.foobar"
    },
    {
        "name": "hello-world",
        "path": "$.metadata.hello.world"
    }
]

To test out that everything is working properly in so far as we are getting the right values.

    @Test
    public void readPopertiesFromYaml() {
        List<JsonPathProperties.Metadata> metadataList = properties.getMetadata();
        long version = properties.getVersion();
        JsonPathProperties.Errors errors = properties.getErrors();

        assertEquals(version, 1234567890);
        assertEquals(metadataList.get(0).getName(), "foobar");
        assertEquals(metadataList.get(0).getGroup(), "metadata");
        assertEquals(metadataList.get(0).getPath(), "$.metadata.foobar");
        assertEquals(errors.getClaims(), "$.errors.claims[0:]");
        assertEquals(errors.getResolutions(), "$.errors.resolutions[0:].offer");
    }

Similar Posts: