How do I merge two structs, where one field in the struct is itself a struct?
52 views (last 30 days)
Show older comments
I'm trying to merge two structs. Both structs have many matching fields, but both structs also have one or more fields that are not in the other struct.
When I've needed to do this in the past, I've converted the structs to tables, using struct2table, then used outerjoin to merge the tables, then converted the merged table back to a struct with table2struct.
However, in this case, one of the fields in the struct is itself a struct. This is causing outerjoin to error.
I thought about "brute-forcing" things by taking the field that's a struct out, using my standard process (i.e. struct2table, outerjoin, table2struct), then merging the problem field back in. However, it's not obvious to me that this would always work. Plus, I'd like a more "elegant" solution, if one is available.
Below is a "toy" example showing what I'm trying to do, both the current case (with a struct field) and the more typical case (without a struct field). Toggle the useStructVar Boolean variable to see the two situations.
obj.Experiment = struct('Measurement',struct([]));
useStructVar = true; % true = "Real-world" case, false = Debugging case
nonStructVar01 = 'nonStructVar01';
nonStructVar02 = 'nonStructVar02';
structVar01 = struct('Type', 'Type01',...
'Material', 'Material01',...
'Layer', 'Layer01');
structVar02 = struct('Type', 'Type02',...
'Material', 'Material02',...
'Layer', 'Layer02');
if useStructVar
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', structVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', structVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', structVar02);
else
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', nonStructVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', nonStructVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', nonStructVar02);
end
index = 1;
for s = 1:length(obj.Space)
for meas = obj.Space(s).Measurement
if isempty(obj.Experiment.Measurement)
obj.Experiment.Measurement = meas;
else
try
obj.Experiment.Measurement(index) = meas;
catch ME
switch ME.identifier
% This case catches the
% 'Subscripted assignment betweeen dissimilar structures'
% error.
case 'MATLAB:heterogeneousStrucAssignment'
currentMeasurementTable = struct2table(obj.Experiment.Measurement,'AsArray',true);
addedMeasurementTable = struct2table(meas, 'AsArray', true);
updatedMeasurementTable = outerjoin(currentMeasurementTable,...
addedMeasurementTable,...
'MergeKeys', true);
obj.Experiment.Measurement = table2struct(updatedMeasurementTable);
disp(''); % DEBUGGING
otherwise
rethrow(ME)
end % End "switch ME.identifier"
end % End "try/catch" block
end % End "if isempty(obj.Experiment.Measurement)
index = index + 1;
end % End "for meas = obj.Space(s).Measurement"
end % End "for s = 1:length(obj.Space)"
% At the end, if useStructVar = true, I get the following error:
%
% Error using tabular/outerjoin (line 141)
% Error when finding unique values for left and right key variables 'Device' and 'Device':
% Undefined function 'sort' for input arguments of type 'struct'..
%
% Error in MY_MATLAB_DEMO_MFILE (line 80)
% updatedMeasurementTable = outerjoin(currentMeasurementTable,...
%
%==========================================================================
%
% If useStructVar = false, then obj.Experiment.Measurement "looks like":
% MeasurementId Layer Device numberDevices
% 'Measurement01' 'ABC' 'nonStructVar01' []
% 'Measurement02' 'XYZ' 'nonStructVar01' []
% 'Measurement03' 'DEF' 'nonStructVar02' '1'
% 'Measurement04' 'UVW' 'nonStructVar02' '1'
% 'Measurement05' 'XYZ' 'nonStructVar02' []
% which is what I expect.
0 Comments
Answers (2)
Voss
on 14 Nov 2024 at 18:13
Setting up obj like you have (with clear obj at the top so that it doesn't contain anything left over from previous runs):
clear obj
obj.Experiment = struct('Measurement',struct([]));
useStructVar = true; % true = "Real-world" case, false = Debugging case
nonStructVar01 = 'nonStructVar01';
nonStructVar02 = 'nonStructVar02';
structVar01 = struct('Type', 'Type01',...
'Material', 'Material01',...
'Layer', 'Layer01');
structVar02 = struct('Type', 'Type02',...
'Material', 'Material02',...
'Layer', 'Layer02');
if useStructVar
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', structVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', structVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', structVar02);
else
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', nonStructVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', nonStructVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', nonStructVar02);
end
One approach that may work for your purposes is to standardize the Measurement struct in each element of obj.Space so that they all contain the same set of fields. (Actually, not in obj.Space itself but a temporary variable S, which is initially equal to obj.Space, so as not to modify obj.Space, in case that's a concern.) This requires supplying values for any missing fields. (In the example below I'm using empty numeric arrays [] for those missing values, but you can use something else if it matters, possibly different values depending on the name and/or class of the field.) Then obj.Experiment.Measurement can easily be constructed by concatenating all the Measurement structs together.
% temporary variable S, so as not to change obj.Space
S = obj.Space;
% set of field names encountered so far
F = {};
for ii = 1:numel(S)
% fieldnames of current S(ii).Measurement
Fii = fieldnames(S(ii).Measurement);
% augment S(ii).Measurement with [] for any fields that have been
% encountered previously but that S(ii).Measurement lacks
Fnew = F(~ismember(F,Fii));
for kk = 1:numel(Fnew)
[S(ii).Measurement.(Fnew{kk})] = deal([]);
end
% augment previous S(jj).Measurement (jj = 1,...,ii-1) with [] for any
% fields that exist in S(ii).Measurement but that haven't been
% encountered previously
Fnew = Fii(~ismember(Fii,F));
for kk = 1:numel(Fnew)
for jj = 1:ii-1
[S(jj).Measurement.(Fnew{kk})] = deal([]);
end
end
% append new field names to the set of encountered field names F
F = [F; Fnew]; %#ok<AGROW>
% now each S(jj).Measurement (jj = 1,..,ii-1) has the same set of
% fields, which is F
% continue on to the next S(ii) ...
end
% now each S(ii).Measurement has the same set of fields, so (assuming their
% sizes are compatible) they are able to be concatenated
obj.Experiment.Measurement = [S.Measurement];
Check the result:
% convert to table to check the result
disp(struct2table(obj.Experiment.Measurement))
Stephen23
on 14 Nov 2024 at 18:14
You can always use a loop and DIY:
obj.Experiment = struct('Measurement',struct([]));
useStructVar = true; % true = "Real-world" case, false = Debugging case
nonStructVar01 = 'nonStructVar01';
nonStructVar02 = 'nonStructVar02';
structVar01 = struct('Type', 'Type01',...
'Material', 'Material01',...
'Layer', 'Layer01');
structVar02 = struct('Type', 'Type02',...
'Material', 'Material02',...
'Layer', 'Layer02');
if useStructVar
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', structVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', structVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', structVar02);
else
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', nonStructVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', nonStructVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', nonStructVar02);
end
index = 1;
for s = 1:length(obj.Space)
for meas = obj.Space(s).Measurement
if isempty(obj.Experiment.Measurement)
obj.Experiment.Measurement = meas;
else
try
obj.Experiment.Measurement(index) = meas;
catch ME
switch ME.identifier
% This case catches the
% 'Subscripted assignment betweeen dissimilar structures'
% error.
case 'MATLAB:heterogeneousStrucAssignment'
fld = fieldnames(meas);
for k = 1:numel(fld)
obj.Experiment.Measurement(index).(fld{k}) = meas.(fld{k});
end
otherwise
rethrow(ME)
end % End "switch ME.identifier"
end % End "try/catch" block
end % End "if isempty(obj.Experiment.Measurement)
index = index + 1;
end % End "for meas = obj.Space(s).Measurement"
end % End "for s = 1:length(obj.Space)"
% At the end, if useStructVar = true, I get the following error:
%
% Error using tabular/outerjoin (line 141)
% Error when finding unique values for left and right key variables 'Device' and 'Device':
% Undefined function 'sort' for input arguments of type 'struct'..
%
% Error in MY_MATLAB_DEMO_MFILE (line 80)
% updatedMeasurementTable = outerjoin(currentMeasurementTable,...
%
%==========================================================================
%
% If useStructVar = false, then obj.Experiment.Measurement "looks like":
% MeasurementId Layer Device numberDevices
% 'Measurement01' 'ABC' 'nonStructVar01' []
% 'Measurement02' 'XYZ' 'nonStructVar01' []
% 'Measurement03' 'DEF' 'nonStructVar02' '1'
% 'Measurement04' 'UVW' 'nonStructVar02' '1'
% 'Measurement05' 'XYZ' 'nonStructVar02' []
% which is what I expect.
obj.Experiment.Measurement
obj.Experiment.Measurement.Device
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!