Reading from yaml is showing null values - java

To read from yaml file i am doing the following
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties("partners")
public class YAMLConfig {
private Map<String,String> partners = new HashMap<>();
public void setPartners(Map<String, String> partners) {
this.partners = partners;
}
public Map<String, String> getPartners() {
return partners;
}
}
yaml file
person2:
name: bbb
addresses:
to: jiang
bit: su
partners:
p1: wallet
p2: wallet
p3: wallet
And in other Java file i am Autowiring to get YamlConfig above
#Service
public class ObjectModificationService {
#Autowired
YAMLConfig yamlConfig;
public JSONObject modify(JSONObject jsonObject ) {
String type = yamlConfig.getPartners().get(partnerName.toLowerCase());
}
}
I am not getting any type above , also checked yamlConfig.getPartners() in debug it is coming as null. I was following this tutorial: https://www.baeldung.com/spring-yaml, but then again IDE is showing error when i put ConfigurationProperties without ConfigurationProperties("partners").

In your yml configuration class, you have the properties annotation set to "partners", this means that the YAMLConfig class will attempt to read the partners section only. The "person2" section will not be read.
To retrieve the dependency injected values, simply call: yamlConfig.getPartners()
Also, be sure that the application.yml is in the resources directory and/or in the same directory as the .jar

you have specified Partners map accepts String values. so, try Map value formatted with quotes ('') in yaml config file
person2:
name: bbb
addresses:
to: jiang
bit: su
partners:
p1: 'wallet'
p2: 'wallet'
p3: 'wallet'

Related

map of map as Bean injection in springboot configuration?

i usually use springboot to inject Beans and use patterns like
in a application.properties file
platform.accounts.acbot71.name=acbot71
platform.accounts.acbot71.secret=secret1
platform.accounts.acbot72.name=acbot72
platform.accounts.acbot72.secret=secret2
in a springboot configuration file
#ConfigurationProperties(prefix = "platform.accounts")
#Bean
public Map<String, PlatformAccount> platformAccounts() {
return new TreeMap<>();
}
with PlatformAccount as a pojo with classic getters and setters
String name;
String email;
All is ok. But now, i'd like to do an advanced configuration with 'inner' map
for each value of the existing map.
I'd like to build an application properties like..
platform.accounts.acbot71.name=acbot71
platform.accounts.acbot71.secret=secret1
platform.accounts.acbot71.subaccount1.name=sub1
platform.accounts.acbot71.subaccount1.secret=secret1
platform.accounts.acbot71.subaccount2.name=sub2
platform.accounts.acbot71.subaccount2.secret=secret2
platform.accounts.acbot72.name=acbot72
platform.accounts.acbot72.secret=secret2
platform.accounts.acbot72.subaccount1.name=sub1
platform.accounts.acbot72.subaccount1.secret=secret1
Purpose is to inject a second map into each object of type PlatformAccount
(i.e. the value of the first map)
How can i do that ?
any code as example ?
I'd do it in application.yml instead, and tweak the properties to have a subAccounts map.
platform:
accounts:
acbot71:
name: acbot71
secret: secret1
subAccounts:
subaccount1:
name: sub1
secret: secret1
subaccount2:
name: sub2
secret: secret2
acbot72:
name: acbot72
secret: secret2
subAccounts:
subaccount1:
name: sub1
secret: secret1
I guess it would look like this in application.properties...
platform.accounts.acbot71.name=acbot71
platform.accounts.acbot71.secret=secret1
platform.accounts.acbot71.subAccounts.subaccount1.name=sub1
platform.accounts.acbot71.subAccounts.subaccount1.secret=secret1
platform.accounts.acbot71.subAccounts.subaccount2.name=sub2
platform.accounts.acbot71.subAccounts.subaccount2.secret=secret2
platform.accounts.acbot72.name=acbot72
platform.accounts.acbot72.secret=secret2
platform.accounts.acbot72.subAccounts.subaccount1.name=sub1
platform.accounts.acbot72.subAccounts.subaccount1.secret=secret1
And your PlatformAccount class could look like this.
public class PlatformAccount {
private String name;
private String secret;
private Map<String, PlatformAccount> subAccounts;
}
I've never tried anything like this, Spring might freak out about the nested identical class. If it does, you would have to make a SubAccount config class.

Unable to map application properties to object with dynamic property key

I am trying to get application property object by value, i already did this in Java, but from some reason using Kotlin i can not manage to do it.
So basically what i have is list of application properties that looks like this:
ee.car.config.audi.serial=1
ee.car.config.audi.base=platform1
ee.car.config.bmw.serial=2
ee.car.config.bmw.base=platform2
so as you can see car identifiers (audi,bmw,peugeot,etc..) are dynamic, and i need simply by serial value to get object that represents the specific car and by car key(audi, bmw) to get all other properties.
And what i did is simple, i created configuration properties class like this:
#Configuration
#ConfigurationProperties(prefix = "ee.car")
data class FesEngineKeys(
val config: HashMap<String, EeCarConfigParam> = HashMap()
) {
fun getOrDefaultEEConfig(engineKey: String): EeCarConfigParam? {
return config.getOrDefault(engineKey, config["audi"])
}
And then object to map keys after dynamic value:
data class EeCarConfigParam {
var serial: String,
var base: String
}
But problem here is, in FesEngineKeys class, config property is empty, it looks like EeCarConfigParam can not be mapped.
Also interesting part is when i change:
val config: HashMap<String, EeCarConfigParam> = HashMap() to
val config: HashMap<String, String> = HashMap()
then i can see that config param is populated with all the values.
This code already works in Java and it looks like this:
#Configuration
#Getter
#Setter
#ConfigurationProperties(prefix = "ee.car")
public class FESEngineKeys {
private Map<String, EeCarConfigParam> config = new HashMap<>();
public EeCarConfigParam getOrDefaultEEConfig(String engineKey) {
return config.getOrDefault(engineKey, config.get("audi"));
}
public EeCarConfigParam findBySerial(String serial) {
return config.values().stream().filter(cfg -> cfg.getSerial().equalsIgnoreCase(serial)).findFirst().orElse(null);
}
}
#Data
public class EeCarConfigParam {
private String serial;
private String base;
}
I really don't know why in the Kotlin case it is not working, i probably made some very basic mistake, and i would really appreciate if anyone can explain what is happening here
Okay i got it.
According to that: https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/boot-features-kotlin.html the support for what you want is very limited.
I made it working like that - not pretty nice :-( :
#ConfigurationProperties(prefix = "ee.car")
class FesEngineKeyProperties() {
var config: MutableMap<String, EeCarConfigParam?>? = mutableMapOf()
fun getBase(serial: String): String {
if(config == null) return ""
return config!!["audi"]?.base ?: ""
}
}
class EeCarConfigParam() {
lateinit var serial: String
lateinit var base: String
}
#SpringBootApplication
#EnableConfigurationProperties(FesEngineKeyProperties::class)
class SandboxApplication
fun main(args: Array<String>) {
runApplication<SandboxApplication>(*args)
}
I was able to handle this issue, it is somehow related to kotlin, because once when i instead of this:
data class EeCarConfigParam {
var serial: String,
var base: String
}
used "norma" Java class, everything started working, so all code from my question stays the same, only difference is this: instead of Kotlin EeCardConfigParam i created Java class like this:
public class EeCarConfigParam {
private String publicUrl;
private String base;
}
Note: with all default getters, setters, equals, hash and toString methods.

Environment variables for list in spring boot configuration

For my Spring Boot application, I am trying to use an environment variable that holds the list of properties.topics in application.yml (see configuration below).
properties:
topics:
- topic-01
- topic-02
- topic-03
I use the configuration file to populate properties bean (see this spring documentation), as shown below
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
#ConfigurationProperties("properties")
public class ApplicationProperties {
private List<String> topics = new ArrayList<>();
public void setTopics(List<String> topics) {
this.topics = topics;
}
public List<String> getTopics() {
return this.topics;
}
}
With the use of environment variable, I can have the list's content change without changing the application.yml. However, all examples that I could find so far only for cases where an environment variable holding only single value, not a collection of values in my case.
Edit:
To make it clear after #vancleff's comment, I do not need the values of the environment variable to be saved to application.yml.
Another edit:
I think by oversimplifying my question, I shoot myself in the foot. #LppEdd answer works well with the example given in my question. However, what happens if instead of a collection of simple string topic names, I need a bit more complex structure. For example, something like
properties:
topics:
-
name: topic-01
id: id-1
-
name: topic-02
id: id-2
-
name: topic-03
id: id-3
a bit late for the show but, I was facing the same problem and this solves it
https://github.com/spring-projects/spring-boot/wiki/Relaxed-Binding-2.0#lists-1
MY_FOO_1_ = my.foo[1]
MY_FOO_1_BAR = my.foo[1].bar
MY_FOO_1_2_ = my.foo[1][2]`
So, for the example in the question:
properties:
topics:
-
name: topic-01
id: id-1
-
name: topic-02
id: id-2
-
name: topic-03
id: id-3
The environment variables should look like this:
PROPERTIES_TOPICS_0_NAME=topic-01
PROPERTIES_TOPICS_0_ID=id-01
PROPERTIES_TOPICS_1_NAME=topic-02
PROPERTIES_TOPICS_1_ID=id-02
PROPERTIES_TOPICS_2_NAME=topic-03
PROPERTIES_TOPICS_2_ID=id-03
Suggestion, don't overcomplicate.
Say you want that list as an Environment variable. You'd set it using
-Dtopics=topic-01,topic-02,topic-03
You then can recover it using the injected Environment Bean, and create a new List<String> Bean
#Bean
#Qualifier("topics")
List<String> topics(final Environment environment) {
final var topics = environment.getProperty("topics", "");
return Arrays.asList(topics.split(","));
}
From now on, that List can be #Autowired.
You can also consider creating your custom qualifier annotation, maybe #Topics.
Then
#Service
class TopicService {
#Topics
#Autowired
private List<String> topics;
...
}
Or even
#Service
class TopicService {
private final List<String> topics;
TopicService(#Topics final List<String> topics) {
this.topics = topics;
}
...
}
What you could do is use an externalized file.
Pass to the environment parameters the path to that file.
-DtopicsPath=C:/whatever/path/file.json
Than use the Environment Bean to recover that path. Read the file content and ask Jackson to deserialize it
You'd also need to create a simple Topic class
public class Topic {
public String name;
public String id;
}
Which represents an element of this JSON array
[
{
"name": "topic-1",
"id": "id-1"
},
{
"name": "topic-2",
"id": "id-2"
}
]
#Bean
List<Topic> topics(
final Environment environment,
final ObjectMapper objectMapper) throws IOException {
// Get the file path
final var topicsPath = environment.getProperty("topicsPath");
if (topicsPath == null) {
return Collections.emptyList();
}
// Read the file content
final var json = Files.readString(Paths.get(topicsPath));
// Convert the JSON to Java objects
final var topics = objectMapper.readValue(json, Topic[].class);
return Arrays.asList(topics);
}
Also facing the same issue , fixed with having a array in deployment.yaml from values.yml replacing the default values of application.yml
example as :
deployment.yml -
----------------
env:
- name : SUBSCRIBTION_SITES_0_DATAPROVIDER
value: {{ (index .Values.subscription.sites 0).dataprovider | quote }}
- name: SUBSCRIBTION_SITES_0_NAME
value: {{ (index .Values.subscription.sites 0).name | quote }}
values.yml -
---------------
subscription:
sites:
- dataprovider: abc
name: static
application.yml -
------------------
subscription:
sites:
- dataprovider: ${SUBSCRIBTION_SITES_0_DATAPROVIDER:abcd}
name: ${SUBSCRIBTION_SITES_0_NAME:static}
Java Code :
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "subscription")
public class DetailsProperties {
private List<DetailsDto> sites;
DetailsProperties() {
this.sites = new ArrayList<>();
}
}
Pojo mapped :
#Getter
#Setter
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class DetailsDto {
private String dataprovider;
private String name;
}
I built a quick little utility to do this.
import java.util.LinkedList;
import java.util.List;
import org.springframework.core.env.Environment;
/**
* Convenience methods for dealing with properties.
*/
public final class PropertyUtils {
private PropertyUtils() {
}
public static List<String> getPropertyArray(Environment environment, String propertyName) {
final List<String> arrayPropertyAsList = new LinkedList<>();
int i = 0;
String value;
do {
value = environment.getProperty(propertyName + "[" + i++ + "]");
if (value != null) {
arrayPropertyAsList.add(value);
}
} while (value != null);
return arrayPropertyAsList;
}
}
You could modify this without too many changes to support multiple fields as well. I've seen similar things done to load an array of database configurations from properties.

Getting subset of application.properties by prefix

I have (partly) this application.properties in my Spring Boot app:
spring.main.banner-mode = off
app.set.a = 100
app.set.b = abc
app.set.c
# ...
I want to get injected a Properties object with all keys/values with prefix "app.". Something directly like this:
#Value(value="${app.*}")
private Properties appProperties; // this obviously won´t work
I have not tried with Properties. But this is how you do with a Map.
application.yml:
test:
my-map:
key1: value1
key2: value2
Java:
#Service
#ConfigurationProperties(prefix="test")
public class MyService {
private Map<String, String> myMap = new HashMap<>(); // add getter
}

Spring Boot - Load multiple YAML files

I'm using Spring boot for my project and trying to load yaml files so that I can use the data in the files in my project. For example I have content in my application.yml like below.
currency:
code:
840: 2
484: 2
999: 0
And in my code for reading the content from application.yml I have a class like this.
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Configuration
#ConfigurationProperties(prefix = "currency")
public class Currency {
private Map<String, String> code;
public Map<String, String> getCode() {
return code;
}
public void setCode(Map<String, String> code) {
this.code = code;
}
}
And If I print it in the test class
public class Test{
#Autowired
Currency currency;
Map<String, String> test = currency.getCode();
for (Map.Entry<String, String> entry : test.entrySet()) {
System.out.println("Key : " + entry.getKey() + " Value : " + entry.getValue());
}
}
I'm getting like below which is perfect.
Key : 840 Value : 2
Key : 484 Value : 2
Key : 999 Value : 0
This is working if I keep the application.yml in my jar itself or I can read it by placing it in git repo aswell.
I tried keeping the content in currency.yml and in my application.yml I tried to use spring.config.location, so that I can read the content from currency.yml directly but it didn't work.
I would like to load files like currency.yml and codes.yml etc... which are custom yml files so that I can read multiple files content and use in my application. Are there any annotations or some approach I can use to load custom.yml files?
In the .yml file of the project add below content.
spring:
profiles:
include:
- currency
Note: You can refer to multiple .yml files this way if you want. You need additional .yml files and Config classes like below example.
You need to have another .yml file which is application-currency.yml
In application-currency.yml you can add currencies, like below
currencies:
mappings:
USD:
fraction: 2
symbol: $
text: "US Dollar"
MXN:
fraction: 2
symbol: $
text: "Mexican Peso"
Your Configuration class would look like below.
package com.configuration;
import com.Currency;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
#Component
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "currencies")
public class CurrencyConfiguration {
private Map<String, Currency> mappings;
public Map<String, Currency> getMappings() {
return mappings;
}
public void setMappings(Map<String, Currency> mappings) {
this.mappings = mappings;
}
}
Wherever you need to use the currency details you can get by calling as shown below.
#Autowired
private CurrencyConfiguration currencyConfiguration;
String currencyCodeAlpha3 = "USD";
Currency currency = currencyConfiguration.getMappings().get(currencyCodeAlpha3);
You can use spring.config.location to specify paths to additional config files as a comma separated list.
java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

Categories

Resources