Not having variables support complex structure and not being able to store parameters as a template prevents what would be simple solutions for sure.
One way that I've worked around this which is reasonable in the end, is having layered templates.
Have your primary template that does the core work, with whatever complex parameters you need.
Create another secondary template that simply calls the core template, but actually specifying the complex objects to be passed in and just passing through the other parameters.
In the pipeline, reference the secondary template, where it would have the same parameters as the primary, with the exception it wouldn't have the complex parameters.
Example:
primary template - primary_template.yaml
parameters:
- name: environments
type: object
- name: releaseName
type: string
- name: imageTag
type: string
stages:
- stage: MyStage
jobs:
- deployment: Deployment
pool:
name: myPoolName
environment: MyEnvironment
strategy:
runOnce:
deploy:
steps:
XXXXXX
Secondary template (note the complex parameter is removed) - secondary_template.yaml
parameters:
- name: imageTag
type: string
- name: releasename
type: string
stages:
- template: primary_template.yaml@AzDoTemplates
parameters:
imageTag: ${{ parameters.imageTag }}
releaseName: ${{ parameters.releaseName }}
environments:
- name: Development
mylist:
- Something1
- Something2
Then in your pipeline(s):
- template: secondary_template.yaml@AzDoTemplates
parameters:
releaseName: ${{ variables.releaseName }}
imageTag: $(Build.BuildNumber)
So in the end, you are able to store the parameters in a template. You can then have multiple secondary templates for different sets of reusable parameters, without having to actually define them in the pipeline. If you have a change, it's in one place, the secondary template versus all of the pipelines.