Modify an attrset in Nix

I find that it’s difficult to modify an attrset in Nix if it’s nested. E.g., if I want to remove a.b.c from

v = { a.b = { c = 1; d = 2; } }

I need to use

v // { a = t.a // { b = builtins.removeAttrs t.a.b [ "c" ]; }; }

If I want to remove a.b.c.d.e… the code will be very verbose. However, if I want to do the same thing with jq I just need

del(.a.b.c.d.e)

Is there any way do the same thing in Nix easily?

There certainly is some function for recursive merging, but that won’t help with deletion.

The nixpkgs lib has

    /* Return if an attribute from nested attribute set exists.

     Example:
       x = { a = { b = 3; }; }
       hasAttrByPath ["a" "b"] x
       => true
       hasAttrByPath ["z" "z"] x
       => false

    Type:
      hasAttrByPath :: [String] -> AttrSet -> Bool
  */
  hasAttrByPath =
    # A list of strings representing the attribute path to check from `set`
    attrPath:
    # The nested attribute set to check
    e:
    let attr = head attrPath;
    in
      if attrPath == [] then true
      else if e ? ${attr}
      then hasAttrByPath (tail attrPath) e.${attr}
      else false;

  /* Filter an attribute set recursively by removing all attributes for
     which the given predicate return false.

     Example:
       filterAttrsRecursive (n: v: v != null) { foo = { bar = null; }; }
       => { foo = {}; }

     Type:
       filterAttrsRecursive :: (String -> Any -> Bool) -> AttrSet -> AttrSet
  */
  filterAttrsRecursive =
    # Predicate taking an attribute name and an attribute value, which returns `true` to include the attribute, or `false` to exclude the attribute.
    pred:
    # The attribute set to filter
    set:
    listToAttrs (
      concatMap (name:
        let v = set.${name}; in
        if pred name v then [
          (nameValuePair name (
            if isAttrs v then filterAttrsRecursive pred v
            else v
          ))
        ] else []
      ) (attrNames set)
    );

from which you can probably derive a deleteRecursive.

Yes, looks like we need another lib function, or even better, a framework to modify a attrset as jq.

There’s a function called updateManyAttrsByPath that suits your needs. It’s a little verbose, but you can easily build very nice modification functions with it.

Let’s take your example of a deep deletion function on a nested set:

nix-repl> v = { a = { r = { s = 3; t = 4; }; b = { c = 1; d = 2; }; }; }

nix-repl> removeByPath = pathList: set: 
            lib.updateManyAttrsByPath [ 
              { 
                path = lib.init pathList;
                update = old: 
                  lib.filterAttrs (n: v: n != (lib.last pathList)) old;
               }
             ] set


nix-repl> :p v
{ a = { b = { c = 1; d = 2; }; r = { s = 3; t = 4; }; }; }

nix-repl> :p removeByPath [ "a" "b" "d" ] v                              
{ a = { b = { c = 1; }; r = { s = 3; t = 4; }; }; }

It would indeed be nice to have something in pure Nix that came as close to the features and expressiveness of jq, though.

1 Like

It’s hard to find the correct function… Thanks!

What really helps me is Noogle. You can search for all builtin and library functions and filter by types as well. That’s how I found updateManyAttrsByPath as well :slight_smile:

2 Likes