Dumb question. Is there a "safe" version of struct...
# cfml-general
Dumb question. Is there a "safe" version of structGet() or something similar, to get the value of a struct key if it exists, but not blow up or modify the struct by creating new keys if it doesn't? I don't even need multi-layer support at the moment, just the equivalent of structKeyExists(struct, key) ? struct[key] : "". I can do that of course, just wondering if I missed a built-in thing.
what version of CF? The safe navigation operator might work for your use case.
I think it's the elvis operator that's doing the real work in those examples. Afaict, you'd only need the safe navigation operator if you have a middle struct key that shares the same name as a built in scope, e.g.
foo.variables.bar ?: ""
(for some reason, that causes CF to throw an exception, whereas
foo.variables?.bar ?: ""
doesn't). EDIT: looks like this bug was fixed in CF 2018+, so elvis is all you need.
One could also reframe the situation here, and wonder if you ought to be using an object not a struct? If one needs a data structure to follow a certain schema, then - in an OO language - it possibly ought better be an object than a struct which really ought to be used as an uncontrolled collection of key/value pairs.
The problem with using structs is you're decoupling any behaviour that further describes the nature of the data (like: it has to exist or there ought to be an exception; it's fine for it to not exist; etc), and putting the requirement to "know" the behaviour on the calling code, not group it with the data. Now: this might be fine. But it might not be. It might be edging towards a Law of Demeter situation (https://en.wikipedia.org/wiki/Law_of_Demeter). Or it might not. But... think about how yer designing yer code.
@Adam Cameron I appreciate the poke towards better design, always. This is a huge largely legacy app, not really object based at all, except in isolated corners. Much but not all of it has decent separation between data and rendering (which is cf btw), but that's about it for "modern" practices. That said, the specific case I was dealing with is multi-checkboxes on a form. If nothing is checked, the field doesn't exist in the form scope, unless I render an additional hidden field just for that reason, which, no. I was looking to read the values for those fields (multiple multi-checkbox instances on the form) in a safe but concise way, that's all. I don't think Demeter-ization really enters into it. Do you agree, within my constraints?
Yep. form fields are provided to you as a struct (for all intents and purposes), so you gotta play the hand you are dealt. That said - not in scope here, but just chatting - you could wrap your request context (query params, post body, cookies, headers etc) in a RequestContext object that encapsulates the behaviour of dealing with collections of uncontrolled key/value pairs, but that's a bit much for what you want here. If I was writing an app without a framework, that's what I'd do right from the outset though. And I'd expect any framework I use to do likewise. Accessing raw scopes like this always seems like bad encapsulation to me. For what you are doing, your original question is legit. It depends on the meaning of "blow up" though, and what you want to get in place of an explosion. I suspect the save nav suggestion someone made is the correct one.
Understood. My simplified problem statment said it was the form scope, but it's actually not, it's a struct that's passed as an argument to various methods by the not-framework. It doesn't have any smarts though, and other than defaulting possible-missing fields so I don't have this question, I'm not sure it should. As for what to do instead of crash, my utility method takes a default value to return if the field's not there, defaults to empty string. As an aside, moving to ColdBox would be a Useful Improvement, but that's very not on the table.
other than defaulting possible-missing fields so I don't have this question, I'm not sure it should.
That's largely all I'd expect a RequestContext object to implement as far as behaviour goes. Just a uniform interface to that one slightly-not-dumb behaviour. You then have the additional benefit of having that uniform interface to pass around the place. A method receiving a RequestContext documents itself, plus it knows how to use the interface provided. And won't be tempted to try anything dumb. Anyway, this is waaaay off topic now 😉