Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove a field from an object #312

Closed
ant31 opened this issue May 26, 2017 · 27 comments
Closed

Remove a field from an object #312

ant31 opened this issue May 26, 2017 · 27 comments

Comments

@ant31
Copy link

ant31 commented May 26, 2017

I haven't found examples nor a way to easily remove a field from an object.

I can eventually iterate over all key and skip the one I want to remove to build a new object with dict comprehension, but the object would lost hidden fields.

@sparkprime
Copy link
Contributor

std.objectFieldsAll(o) will return the hidden ones too. However all your hidden fields would become visible because of the : in { [f]: v for f in std.objectFieldsAll(o) }

@sparkprime
Copy link
Contributor

Can you get the effect of removing a field just by marking it as hidden? x :: super.x

@ant31
Copy link
Author

ant31 commented Jun 4, 2017

Is there any chance that dict comprehension can include to use hidden too?

@sparkprime
Copy link
Contributor

sparkprime commented Jun 4, 2017

One thing I'd like to do is generalize the object comprehension and merge it with the object literal. This means things like this will be possible:

local fields = std.range(1, 100);
{
    ["field" + i]: true for i in fields if i % 2 == 0,
    ["field" + i]:: true for i in fields if i % 2 == 1,
    lots_of_fields: true if std.length(fields) > 50,
}

There's another request in #90 to extend comprehensions.

What you can do is then:

{
    [k]: obj[k] for k in std.objectFields(obj),
    [k]:: obj[k] for k in std.objectFieldsAll(obj) if !std.objectHas(obj, k),
}

@sparkprime
Copy link
Contributor

It's quite easy from that to build it with all fields except one, just put if k != "foo" at the end.

@sparkprime
Copy link
Contributor

This isn't strictly the same as removing a field from an object though, what it's doing is creating a statically bound copy. E.g. if we took the above code and put it in a function clone(), then:

local obj = {x: 1, y: self.x * 2};
{
    obj1: obj {x: 10},
    obj2: clone(obj) {x: 10},
}

would yield:

{
    "obj1": { "x": 10, "y": 20 },
    "obj2": { "x": 10, "y": 2 }
}

@ant31
Copy link
Author

ant31 commented Jun 8, 2017

@sparkprime Yes, extending object comprehension and with hidden field would solve this issue.
as you suggested:

objectPop(obj, key): { 
    [k]: obj[k] for k in std.objectFields(obj) if k != key, 
    [k]:: obj[k] for k in std.objectFieldsAll(obj) if !std.objectHas(obj, k) && k != key,
 } 

@ant31 ant31 changed the title [help] Remove a field from an object Remove a field from an object Jun 8, 2017
@mbarbero
Copy link

mbarbero commented May 7, 2020

@sparkprime Yes, extending object comprehension and with hidden field would solve this issue.
as you suggested:

objectPop(obj, key): { 
    [k]: obj[k] for k in std.objectFields(obj) if k != key, 
    [k]:: obj[k] for k in std.objectFieldsAll(obj) if !std.objectHas(obj, k) && k != key,
 } 

Unfortunately, the object is not late bound anymore in this case.

@sbarzowski
Copy link
Collaborator

sbarzowski commented May 7, 2020

@mbarbero
Yes. { [k]: obj[k] for k in std.objectFieldsAll(obj) } is not the same as obj. It is merely an object which has all the same values in the same fields. But self is fixed as this point (and super is affected as well).

That said, objectPop solution is still useful for objects which just hold data and don't use self or super.

@mbarbero
Copy link

mbarbero commented May 7, 2020

Indeed, it's still useful. My comment was a bit "raw", sorry about that. I just wanted to note that, if jsonnet ever includes a solution to remove fields from an object, it would be great if the solution would retain the late bounding on target object.

@sbarzowski
Copy link
Collaborator

I've just realized that the proposed implementation of objectPop doesn't actually work. You can't have hidden fields in object comprehensions nor multiple fors there. So the best you can do AFAICT is something like:

objectPop(obj, key): { 
    [k]: obj[k] for k in std.objectFieldsAll(obj) if k != key
} 

And that loses the visibility information.

@ghostsquad
Copy link

I just ran into this, finding that replicas: null is not the same as excluding the replicas field in the manifest in kubernetes for a deployment object.

If the # of replicas is managed by a Horizontal Pod Autoscaler, then you want to ensure that you don't change the # of replicas each time you deploy.

@ghostsquad
Copy link

ghostsquad commented Jan 25, 2021

could you do something like:

objectPop(obj, key): {
    if std.objectHas(obj, k) then
       [k]: obj[k]
    else
       [k]:: obj[k]
    for k in std.objectFieldsAll(obj) if k != key
} 

?

@sbarzowski
Copy link
Collaborator

Not really. The comprehensions only support a single field pattern. Also you can't wrap the fields in ifs like that. If you have a proposition how that could work, we can look into it. Right now you can do it with two comprehensions (one for : and the other for ::) and +.

Perhaps a more direct way of handling this, would be to just add a builtin to remove a field. We would need to specify what it means for self and super. I'm also open to proposals.

Generally, I would recommend removing fields only for "plain" objects anyway and you care only about what fields evaluate to and not automagically maintaining relationships between them. By plain objects I mean objects where each field is visible and concrete (i.e. doesn't depend on other fields through self/super). In such case objectPop is doing what you want.

@DieterVDW
Copy link

I really expected something like CSS's property: unset here, all of the above just feels like complex workarounds.

@sparkprime
Copy link
Contributor

Years ago I thought about adding the facility to remove a field in a mixin. The syntax might look like that, or maybe { null f } or something.

@frimik
Copy link

frimik commented Jun 15, 2022

Right now you can do it with two comprehensions (one for : and the other for ::) and +.

What do you mean right now? I can't do :: at all in object comprehensions, right? ("cannot have hidden fields").

@sbarzowski
Copy link
Collaborator

@frimik
Oh, I'm afraid you're right.

@CertainLach
Copy link
Contributor

Deconstruction proposal partially (this/super becomes frozen) solves this issue: #307

local sourceObject = {
  a: 1,
  b: 2,
  c:: 3,
  fieldToOmit: 4,
};

local {
  fieldToOmit: ?,
  ...targetObject
} = sourceObject;

std.manifestJson(targetObject) == '{"a": 1, "b": 2}'

@frimik
Copy link

frimik commented Jun 29, 2022

I started using [k]: null in an object comprehension + std.prune().

Now I changed methods to using std.mergePatch() instead which makes it slightly cleaner in my particular case.
According to #216 - beware that object gets resolved to JSON at that point?

    local ddMergePatch = {
      [k]: null
      for k in std.objectFields(agent.datadog)
      if std.objectHas(agent.datadog[k], 'kind') && agent.datadog[k].kind != 'PodSecurityPolicy'
    };

    local agentMergePatch =       {
        [k]: null
        for k in std.objectFields(agent)
        if std.objectHas(agent[k], 'kind') && agent[k].kind != 'PodSecurityPolicy'
      };

    std.mergePatch(agent, agentMergePatch) {
      datadog: std.mergePatch(agent.datadog, ddMergePatch),
    },

@dxlr8r
Copy link

dxlr8r commented Dec 12, 2022

Right now you can do it with two comprehensions (one for : and the other for ::) and +.

What do you mean right now? I can't do :: at all in object comprehensions, right? ("cannot have hidden fields").

I saw Jrsonnet mentioned in this thread, and they do support this:

./target/release/jrsonnet -e "local a = {[i[0] + '!']:: i[1] + '!' for i in {a: 1,b: 2,c: 3,}}; a['a!']"
"1!"

You do have to build it yourself though to get this functionality, but looks promising.

@dxlr8r
Copy link

dxlr8r commented Dec 12, 2022

Also, on the topic of object comprehensions and for loops. Recursive functions does not have this issue. I ended up creating my own forEach/map function:

https://github.com/dxlr8r/dxsonnet/blob/main/lib/obj.libsonnet

With a function like that in place, it is simple to create for example a pop function, which can then be called like this:

local obj = import "../lib/obj.libsonnet";
local x = {a: 0, b: 1, c: 2, d:: 3}; 

{
  // remove field `a` from obj `x`
  pop: obj.pop(x, "a"), // returns: `{b: 1, c: 2, d:: 3}`
}

@icereed
Copy link

icereed commented Jun 1, 2023

Hey there,

I went with this approach which allows multiple fields to be ignored.

In my filter.libsonnet file I got this:

{
  // Function to filter an object by excluding specified fields.
  // Parameters:
  // - inputObject: The object to be filtered.
  // - fieldsToIgnore: List of fields to be ignored from the input object.
  filterObjectFields(inputObject, fieldsToIgnore):: 
    // Iterating over the fields in the input object and creating a new object 
    // without the fields specified in `fieldsToIgnore`.
    std.foldl(function(filteredObject, currentField) 
      // If current field is in `fieldsToIgnore`, return the filtered object as is.
      // Otherwise, add the current field to the filtered object.
      (
        if std.member(fieldsToIgnore, currentField) then
          filteredObject
        else
          filteredObject + { [currentField]: inputObject[currentField] }
      ),
      // Starting with an empty object and iterating over each field in the input object.
      std.objectFields(inputObject), {}),
}

Then you can use it like:

local filter = import 'filter.libsonnet';

local fieldsToIgnore = [
  'ignore1',
  'ignore2',
  'ignore3',
];

local objectToFilter = {
  'field1': 'value1',
  'field2': 'value2',
  'ignore1': 'value3',
  // ...
};

// Filter object
filter.filterObjectFields(objectToFilter, fieldsToIgnore)

Works well for my use case but might not be the most optimal solution out there. Maybe that helps someone.

@sparkprime
Copy link
Contributor

sparkprime commented Jun 7, 2023 via email

@mkmik
Copy link
Contributor

mkmik commented Sep 6, 2023

(in response to #312 (comment))

as a simpler workaround to manually rolling recursive functions, fold can be used:

local hide(obj, field=null, pred=function(name) (name == field), map=function(value) value) =
  std.foldl(
    function(acc, f)
      if pred(f.key) then
        acc { [f.key]:: map(f.value) }
      else
        acc { [f.key]: f.value },
    std.objectKeysValues(obj),
    {}
  );

(code with tests https://gist.github.com/mkmik/ba6110b5fa23352f9c9304a6aa51e614)

of course, if one only wants to hide a field with a given name, that's just

local hide(obj, field) = obj { [field]:: std.get(obj, field) };

the object-comprehension is needed if you want to apply some predicates and/or map the values.

(unrelated note: I was pleasantly surprised when I learned that you can have a default argument of a function depend on another argument of the function, e.g. above in the function(name) (name == field) references the previous arg field )

@vergenzt
Copy link

vergenzt commented Jul 17, 2024

I didn't read through all of this issue, but does #1070 address this? Should this issue be closed?

@sbarzowski
Copy link
Collaborator

I don't think we want to implement anything more, so yeah, I'm going to close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests