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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions