Currently the Binder class has a "first wins" policy for binding lists. From the docs:
When lists are configured in more than one place, overriding works by replacing the entire list.
This policy has worked well, however, it also causes problems such as #41669 where users really want to merge lists together.
I think it would be a mistake to attempt to compose lists from elements in different property sources, however, we might be able to offer a feature where distinct lists could be merged together.
Proposal
When property values are defined, the property name could include an additional indicator that is used to determine how the values should be merged. The indicator would be (+) for addAll and (-) for removeAll:
This example shows an auto-configure exclude:
.properties
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration
#---
spring.config.activate.on-profile: noredis
spring.autoconfigure.exclude(+)=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
.yaml
spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchClientAutoConfiguration
---
spring:
config:
activate:
on-profile: noredis
autoconfigure:
exclude(+): org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
More complex examples are:
.properties
my.list-of-names(+)[0].first=Spring
my.list-of-names(+)[0].last=Boot
my.list-of-names(+)[1].first=Spring
my.list-of-names(+)[1].last=Framework
.yaml
my:
list(-):
- remove
- stuff
Limitations
It will be hard to support this syntax with environment variables.
Comment From: philwebb
Perhaps without the brackets. And perhaps only + for now.
Comment From: tmoschou
I'ld be interested to know how this might work for collections of complex types (which might have sub-collections). E.g.
my-collection:
- name: foo
prop: x
sub-collection:
- item1
- item2
- name: bar
prop: y
sub-collection:
- item3
- item4
Removing an item from my-collection or setting a property of an item, without knowing its index might be tricky. E.g
'my-collection[name == "bar"].prop': "z"
We generally in our team have an convention that if
- a collection might need to be mutated and
- the items in that collection have a natural identifier (such as a sub-property name),
then its probably better modelled as map using the sub-property as a key - and in most other cases we don't actually need to mutate existing collections and content with "first wins" policy.
my-collection:
foo:
prop: x
sub-collection:
- item1
- item2
bar:
prop: y
sub-collection:
- item3
- item4