Joseph Gagnon
Joseph Gagnon

Reputation: 2115

How to parse complex YAML into Java object hierarchy (Spring Boot configuration)

I need to define service connection properties which will be used to configure RestTemplates used to make REST endpoint calls to Spring Boot services.

Note: In the future a service discovery mechanism will likely be involved, but I have been instructed NOT to pursue that avenue for the time being.

Service connection properties:

Possible configuration (YAML):

services-config:
  global:
    scheme: http|https
    connectionTimeout: <timeout ms>
    requestTimeout: <timeout ms>
    socketTimeout: <timeout ms>
    defaultKeepAlive: <timeout ms>
    defaultMaxConnPerRoute: <count>
    maxTotalConnections: <count>
  services:
    - id: <ID>          [?]
      name: <name>      [?]
      host: localhost|<host>
      port: <port>
    - id: <ID>          [?]
      name: <name>      [?]
      host: localhost|<host>
      port: <port>
    ...

The intent is to place this information into an application.yml file so that it can be accessed within the Spring application.

Is there a way to pull this information in YAML format into a Java object hierarchy like the following:

class : ServicesConfig
  global : GlobalConnectionProperties
  services : map<String, ServiceConnectionProperties>

class : GlobalConnectionProperties
  scheme : String (maybe enum instead)
  connectionTimeout : int
  requestTimeout : int
  socketTimeout : int
  defaultKeepAlive : int
  defaultMaxConnPerRoute : int
  maxTotalConnections : int

class : ServiceConnectionProperties
  id : String
  name : String
  host : String
  port : int

UPDATE:

Attempted the following and achieved partial success:

ServiceConfiguration.java

@Configuration
public class ServiceConfiguration {

  private ServicesConfig servicesConfig;

  @Autowired
  public ServiceConfiguration(ServicesConfig servicesConfig) {
    this.servicesConfig = servicesConfig;
    System.out.println(servicesConfig);
  }

}

ServicesConfig.java:

@ConfigurationProperties(value = "services-config")
@EnableConfigurationProperties(
    value = {GlobalConnectionProperties.class, ServiceConnectionProperties.class})
public class ServicesConfig {

  private GlobalConnectionProperties               globalProps;
  private Map<String, ServiceConnectionProperties> services;

  public GlobalConnectionProperties getGlobalProps() {
    return globalProps;
  }

  public void setGlobalProps(GlobalConnectionProperties globalProps) {
    this.globalProps = globalProps;
  }

  public Map<String, ServiceConnectionProperties> getServices() {
    return services;
  }

  public void setServices(Map<String, ServiceConnectionProperties> services) {
    this.services = services;
  }

  @Override
  public String toString() {
    return "ServicesConfig [globalProps=" + globalProps + ", services=" + services + "]";
  }

}

application.yml:

services-config:
  global:
    scheme: http
    connectionTimeout: 30000
    requestTimeout: 30000
    socketTimeout: 60000
    defaultKeepAlive: 20000
    defaultMaxConnPerRoute: 10
    maxTotalConnections: 200
  services:
    - id: foo
      name: foo service
      host: 192.168.56.101
      port: 8090
    - id: bar
      name: bar service
      host: 192.168.56.102
      port: 9010

The GlobalConnectionProperties and ServiceConnectionProperties classes have been omitted as they just contain properties, getter/setter methods and toString().

When I run a service, the following appears on the console:

ServicesConfig [globalProps=null, services={0=ServiceConnectionProperties [id=foo, name=foo service, host=192.168.56.101, port=8090], 1=ServiceConnectionProperties [id=bar, name=bar service, host=192.168.56.102, port=9010]}]

I'm actually surprised that the list items were parsed and inserted into the map. I'd like the "id" to be the key instead of a default integer. The odd thing is that the "global" properties have not made it into the map.

What do I need to correct to get the global object items read in?

Is there a way to key the map entries by the item "id" property?

Upvotes: 0

Views: 2360

Answers (1)

Mark
Mark

Reputation: 29129

What do I need to correct to get the global object items read in?

Binding of properties follows Java Bean conventions so in ServicesConfig you need to rename the field and getters/setters from globalProps to global.

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-typesafe-configuration-properties as linked in the comment has more details.

Is there a way to key the map entries by the item "id" property?

For the map entries your yaml syntax is incorrect. See the syntax using : and ? at https://bitbucket.org/asomov/snakeyaml/wiki/Documentation#markdown-header-type-safe-collections. Your map would be

services:
  ? foo
  : { id: foo,
      name: foo service,
      host: 192.168.56.101,
      port: 8090 }
  ? bar
  : { id: bar,
      name: bar service,
      host: 192.168.56.102,
      port: 9010}

Upvotes: 2

Related Questions