How can I dynamically index into a nested struct that contains cell arrays
11 views (last 30 days)
Show older comments
Raphael Guzman
on 11 Jan 2020
Commented: Raphael Guzman
on 13 Jan 2020
For a package, I need a global config to be configured by users from Command Window and available to all package functions and classes.
I need to be able to both get and set a config setting.
Suppose that config is currently defined as:
```
config = struct();
config.stores = { ...
struct( ...
'name', 'local', ...
'type', 'file', ...
'location', '/tmp' ...
), ...
struct( ...
'name', 'external', ...
'type', 's3', ...
'access_key', 'aws_key', ...
'access_secret', 'aws_secret' ...
)
}
```
When a user makes a call to Settings('stores{2}.name'), I would like to have it return 'external'.
If a user needs to update a setting, they should be able to do so with Settings('stores{2}.name', 'aws').
To solve this, I have created a Settings function that simply provides an entrypoint into a Settings class that has a static method with a persistent struct variable, config. This works fine utilizing getfield(config, fieldPath{:}) and setfield(config, fieldPath{:}, value), however, it breaks down when accessing a field containing a cell array (as above). This is because a get such as:
fieldPath = {'stores' {[2]} 'name'};
getfield(config, fieldPath{:})
is translated to
config.stores(2).name
The exact error is "Dot indexing is not supported for variables of this type."
Unfortunately, I cannot guarantee the array will have uniform objects as this configuration is actually being loaded from an external process.
My question is: How can I dynamically index into a nested struct over fields containing cell arrays?
This is my first post, so hoping I can get any feedback and tips on best MATLAB practice to resolve such a use case. So far I have been toying with alternatives utilizing recursive loops and eval.
Many thanks in advance!
Raphael
0 Comments
Accepted Answer
Stephen23
on 11 Jan 2020
Edited: Stephen23
on 12 Jan 2020
Essentially you are attempting to write a MATLAB parser using MATLAB. Such a task is not trivial, but for a very limited subset of known commands it can be possible to create code that performs operations based on the user input without requiring eval (which gets the MATLAB parser to parse arbitrary MATLAB code, but has significant disadvantages related to efficiency and debugging).
You could use subsref and subsasgn, which allow for indexing into any kind of array, including nested structures and cell arrays, no recursion nor eval is required. For example:
% User input character vector:
str = 'stores{2}.name';
% Create a 2xN cell array {index type ; field/index} from that char vector:
tkn = regexp(['.',str],'(\W)(\w+)','tokens'); % assumes the first part is always a field.
tkn = vertcat(tkn{:}).';
tkn(1,:) = strrep(strrep(tkn(1,:),'{','{}'),'(','()');
% Convert linear indices to numeric:
vec = str2double(tkn(2,:));
idx = ~isnan(vec);
tkn(2,idx) = num2cell(num2cell(vec(idx)));
% Create indexing structure from the cell array:
sbs = substruct(tkn{:});
Now you can use that indexing structure as often as you like with subsref (get data out of array):
>> val = subsref(config,sbs)
val = 'external'
Or with subsasgn (allocate data to array):
config = subsasgn(config,sbs,'aws'); % allocate 'aws' to that location
Tip: if the user supplies the indexing structure itself or a cell array something like tkn, then you can simplfiy your code.
More Answers (0)
See Also
Categories
Find more on Construct and Work with Object Arrays in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!