Testing based on the yaml files shared by you, I don't have the same error and the pipeline works fine.
azure-pipelines.yml:
trigger:
- none
pool:
vmImage: ubuntu-latest
extends:
template: pipeline.yml
parameters:
DefaultSettings:
Service1:
Setting1: Value1
Setting2: Value2
Service2:
Setting1: Value1
Environments:
Development:
Settings:
Service1:
Setting2: DevelopmentValue2
Service2:
Setting1: DevelopmentValue1
Test:
Settings:
Service2:
Setting1: TestValue1
pipeline.yml:
parameters:
- name: DefaultSettings
type: object
default: []
- name: Environments
type: object
default: []
stages:
- stage: Pipeline
jobs:
- job: job
steps:
- script: |
echo "ENVIRONMENTS: ${ENVIRONMENTS}"
echo "DEFAULTSETTINGS: ${DEFAULTSETTINGS}"
env:
ENVIRONMENTS: ${{ convertToJson(parameters.Environments) }}
DEFAULTSETTINGS: ${{ convertToJson(parameters.DefaultSettings) }}
- ${{ each environment in parameters.Environments }}:
- template: environment.yml
parameters:
Environment: ${{ environment.Value }}
DefaultSettings: ${{ parameters.DefaultSettings }}
environment.yml:
parameters:
- name: Environment
type: object
default: []
- name: DefaultSettings
type: object
default: []
stages:
- stage:
jobs:
- job: job
steps:
- script: |
echo "ENVIRONMENT: ${ENVIRONMENT}"
echo "DEFAULTSETTINGS: ${DEFAULTSETTINGS}"
env:
ENVIRONMENT: ${{ convertToJson(parameters.Environment) }}
DEFAULTSETTINGS: ${{ convertToJson(parameters.DefaultSettings) }}
- template: environment-stages.yml
parameters:
Settings:
${{ each service in parameters.DefaultSettings }}:
${{ service.Key }}:
${{ each setting in service.Value }}:
${{ setting.Key }}: ${{ coalesce(parameters.Environment.Settings[service.Key][setting.Key], setting.Value) }}
environment-stages.yml:
parameters:
- name: Settings
type: object
default: []
stages:
- stage:
jobs:
- job: job
steps:
- script: |
echo "Settings: ${SETTINGS}"
env:
SETTINGS: ${{ convertToJson(parameters.Settings) }}
Result: