Skip to content

Feature Request: Add deep merge support to CEL maps library #1240

@kahirokunn

Description

@kahirokunn

Feature request checklist

  • There are no issues that match the desired change

Change
Add deep merge support for nested map structures in the CEL maps library.

Currently, map.merge only performs a shallow merge: when two maps share the same key whose value is also a map, the value from the second map completely replaces the value from the first map. This makes it hard to work with configuration-like structures that naturally contain nested maps and lists (for example, Kubernetes-style objects).

The requested change is to provide a deep merge operation, either as:

  • A new function (e.g. mergeDeep, deepMerge, or similar), or
  • An additional mode/parameter on the existing merge function,

with the following behavior:

  • Non-map values: values from the second map overwrite values from the first map (same behavior as current shallow merge).

  • Map values: if both maps contain map values for the same key, recursively merge those nested maps instead of replacing them entirely.

  • List values containing maps: support merging list items that represent configuration objects, by matching on an identifying key (e.g. name for Kubernetes containers).

    • When two list elements share the same identifier, deep merge those elements.
    • When identifiers differ, keep all elements (concatenate the lists).
  • Lists of primitive values: behavior could be configurable (concatenate vs. replace) or follow a simple, documented default.

This feature would significantly improve ergonomics when expressing configuration overlays and patches directly in CEL.


Example

Nested maps:

// Current behavior (shallow merge)
{'a': {'x': 1, 'y': 2}}.merge({'a': {'y': 3, 'z': 4}})
// => {'a': {'y': 3, 'z': 4}}

// Desired deep merge behavior
{'a': {'x': 1, 'y': 2}}.mergeDeep({'a': {'y': 3, 'z': 4}})
// => {'a': {'x': 1, 'y': 3, 'z': 4}}

Lists of maps (e.g. Kubernetes containers):

// Current behavior (shallow merge)
{'containers': [{'name': 'app', 'image': 'v1'}]}.merge({
  'containers': [
    {
      'name': 'app',
      'env': [{'name': 'DEBUG', 'value': 'true'}],
    },
  ],
})
// => {'containers': [{'name': 'app', 'env': [{'name': 'DEBUG', 'value': 'true'}]}]}

// Desired deep merge behavior
{'containers': [{'name': 'app', 'image': 'v1'}]}.mergeDeep({
  'containers': [
    {
      'name': 'app',
      'env': [{'name': 'DEBUG', 'value': 'true'}],
    },
  ],
})
// => {'containers': [
//      {'name': 'app', 'image': 'v1',
//       'env': [{'name': 'DEBUG', 'value': 'true'}],
//      },
//    ]}

Alternatives considered

  • Performing deep merge logic manually in CEL expressions using combinations of has, map, filter, and fold – this quickly becomes verbose and error-prone for non-trivial structures.
  • Implementing deep merging outside of CEL (e.g. in the host language) before or after evaluation – this reduces the benefit of CEL as a configuration/patching language and complicates pipelines.
  • Encoding nested configuration in a flatter structure to avoid deep merges – this makes expressions and schemas less natural and harder to read/maintain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions