Compares two structs to see if they have identical fields and/or values struct1 and struct2 are the only required arguments. the rest of the arguments are tag,value pairs that specify how the structs will be compared. supported tags and values: 'values' (true or false): specifies whether field values will be compared or not. 'substruct' (true or false): specifies whether to check only whether struct1 is contained in struct2 (struct2 simply has more fields and values than struct1 but otherwise are equal). Otherwise the structs will be compared for an exact match. 'types' (true or false): specifies whether or not to check the type (class) of the fields for a positive match. This can only be set to false if 'values' is also false. Otherwise, types will automatically be checked. 'ignore_fieldnames': a cell array of strings that, if encountered as a fieldname, will be skipped, and will not be compared. 'subsetIsOK': a cell array of strings identifying fieldnames for which a subset is of values is OK. Example: If the value of the fieldname 'HalfDecayTimes' in struct1 contains ([0.2 2]), the values of a matched fieldname in struct2 would be deemed equal if it contained the desired values and any others, e.g. [0.2 2 4 8]. defaults if not specified are 'values',true and 'contained',false OUTPUTS structsAreEqual - 1 or 0, whether or not struct equality or substruct is true firstViolation - returns a cell array of fields (or subfields) that resulted in the first violation of equality (or substruct-ness). This isn't a complete list and is only meant to aid in finding inequalities. violationReason - provides a concise descriptor of the type of violation that occurred. This is short and simple to allow for easy string matching if a more elaborate report is desired in a higher level script. Copyright (c) 2007-2012 The Regents of the University of California All Rights Reserved. Author(s) 2007 - Stefan Tomic, first version 10/8/2008 - Stefan Tomic, extracted from check_anal_exist and added tag,value functionality 12/30/2008 - Fred Barrett, added support for multi-dimensional cell arrays as field values 02/24/2009 - Fred Barrett, switched cell checking to compare_cells.m, also added handling of struct arrays 11/11/2009 - Fred Barrett, added 'ignore_fieldnames' option 01Nov2011 - Petr Janata, added ability to specify those fieldnames for which specification of a subset of values is OK. 30Oct2012 - Improved handling of ignore_fieldnames; eliminated instances of strmatch() 29Dec2014 - PJ - made field type mismatch non-fatal if both fields are empty
0001 function [structsAreEqual,firstViolation,violationReason] = compare_structs(struct1,struct2,varargin) 0002 % 0003 % Compares two structs to see if they have identical fields and/or values 0004 % 0005 % struct1 and struct2 are the only required arguments. the rest of the 0006 % arguments are tag,value pairs that specify how the structs will be 0007 % compared. 0008 % 0009 % supported tags and values: 0010 % 'values' (true or false): specifies whether field values will be 0011 % compared or not. 0012 % 'substruct' (true or false): specifies whether to check only 0013 % whether struct1 is contained in struct2 (struct2 simply has 0014 % more fields and values than struct1 but otherwise are 0015 % equal). Otherwise the structs will be compared for an exact 0016 % match. 0017 % 'types' (true or false): specifies whether or not to check the 0018 % type (class) of the fields for a positive match. This can only 0019 % be set to false if 'values' is also false. Otherwise, types 0020 % will automatically be checked. 0021 % 'ignore_fieldnames': a cell array of strings that, if encountered 0022 % as a fieldname, will be skipped, and will not be compared. 0023 % 'subsetIsOK': a cell array of strings identifying fieldnames for which 0024 % a subset is of values is OK. Example: If the value of the 0025 % fieldname 'HalfDecayTimes' in struct1 contains ([0.2 2]), the 0026 % values of a matched fieldname in struct2 would be deemed equal if 0027 % it contained the desired values and any others, e.g. [0.2 2 4 8]. 0028 % 0029 % defaults if not specified are 'values',true and 'contained',false 0030 % 0031 % OUTPUTS 0032 % structsAreEqual - 1 or 0, whether or not struct equality or 0033 % substruct is true 0034 % 0035 % firstViolation - returns a cell array of fields (or subfields) 0036 % that resulted in the first violation of equality (or 0037 % substruct-ness). This isn't a complete list 0038 % and is only meant to aid in finding 0039 % inequalities. 0040 % 0041 % violationReason - provides a concise descriptor of the type of 0042 % violation that occurred. This is short and simple to 0043 % allow for easy string matching if a more elaborate 0044 % report is desired in a higher level script. 0045 % 0046 % Copyright (c) 2007-2012 The Regents of the University of California 0047 % All Rights Reserved. 0048 % 0049 % Author(s) 0050 % 2007 - Stefan Tomic, first version 0051 % 10/8/2008 - Stefan Tomic, extracted from check_anal_exist and added tag,value functionality 0052 % 12/30/2008 - Fred Barrett, added support for multi-dimensional cell arrays as field values 0053 % 02/24/2009 - Fred Barrett, switched cell checking to compare_cells.m, 0054 % also added handling of struct arrays 0055 % 11/11/2009 - Fred Barrett, added 'ignore_fieldnames' option 0056 % 01Nov2011 - Petr Janata, added ability to specify those fieldnames for 0057 % which specification of a subset of values is OK. 0058 % 30Oct2012 - Improved handling of ignore_fieldnames; eliminated instances 0059 % of strmatch() 0060 % 29Dec2014 - PJ - made field type mismatch non-fatal if both fields are 0061 % empty 0062 0063 numberTypes = {'int8','uint8','int16','uint16','int32','uint32','int64','uint64','double'}; 0064 firstViolation = {}; 0065 violationReason = ''; 0066 VERBOSE = 0; 0067 0068 if (nargin > 2) 0069 for iarg = 1:2:nargin-2 0070 switch(varargin{iarg}) 0071 case 'values' 0072 checkValues = varargin{iarg+1}; 0073 case 'substruct' 0074 checkSubstruct = varargin{iarg+1}; 0075 case 'types' 0076 checkTypes = varargin{iarg+1}; 0077 case 'ignore_fieldnames' 0078 ignore_fieldnames = varargin{iarg+1}; 0079 case 'subsetIsOK' 0080 subsetIsOK = varargin{iarg+1}; 0081 case 'verbose' 0082 VERBOSE = varargin{iarg+1}; 0083 end 0084 end 0085 end 0086 0087 if ~exist('checkValues','var'), checkValues = true; end 0088 if ~exist('checkSubstruct','var'), checkSubstruct = true; end 0089 if ~exist('checkTypes','var'), checkTypes = true; end 0090 if ~exist('ignore_fieldnames','var') 0091 ignore_fieldnames = {}; 0092 elseif ischar(ignore_fieldnames) 0093 ignore_fieldnames = {ignore_fieldnames}; 0094 end 0095 if ~exist('subsetIsOK','var'), subsetIsOK = {}; end 0096 0097 struct1_fieldNames = fieldnames(struct1)'; 0098 struct2_fieldNames = fieldnames(struct2)'; 0099 0100 struct1_nFields = length(struct1_fieldNames); 0101 struct2_nFields = length(struct2_fieldNames); 0102 0103 fieldNames_intersect = intersect(struct1_fieldNames,struct2_fieldNames); 0104 intersect_nFields = length(fieldNames_intersect); 0105 0106 %the structs must have all fieldnames in common 0107 if(checkSubstruct) 0108 if(struct1_nFields ~= intersect_nFields) 0109 fieldNames_diff = setdiff(struct1_fieldNames,struct2_fieldNames); 0110 for k=1:length(fieldNames_diff) 0111 if isempty(strmatch(fieldNames_diff{k},ignore_fieldnames)) 0112 firstViolation = fieldNames_diff{k}; 0113 violationReason = 'missing_fields_struct2'; 0114 structsAreEqual = 0; 0115 return 0116 end 0117 end 0118 end 0119 else 0120 if(struct1_nFields ~= intersect_nFields || struct2_nFields ~= intersect_nFields) 0121 fieldNames_xor = setxor(struct1_fieldNames,struct2_fieldNames); 0122 for k=1:length(fieldNames_xor) 0123 if isempty(strmatch(fieldNames_xor{k},ignore_fieldnames)) 0124 firstViolation = fieldNames_xor{k}; 0125 violationReason = 'missing_fields_either_struct'; 0126 structsAreEqual = 0; 0127 return 0128 end 0129 end 0130 end 0131 end 0132 0133 % get the size of the structs ... are they struct arrays? 0134 struct1_size = size(struct1); 0135 struct2_size = size(struct2); 0136 0137 if ~all(struct1_size == struct2_size) 0138 firstViolation = 'struct2'; 0139 violationReason = 'struct_arrays_different_size'; 0140 structsAreEqual = 0; 0141 return 0142 else 0143 nstruct_array = prod(struct1_size); % number of elements 0144 end 0145 0146 % if we are checking only for a substruct, the following will 0147 % still work because we are only cycling through the fields of 0148 % struct1 and checking for a match in struct2. If we are checking 0149 % for an exact match, then we have already 0150 % verified in the logic above that both structs have the exactly 0151 % the same fields 0152 0153 for iField = 1:struct1_nFields 0154 0155 struct1_fieldName = struct1_fieldNames{iField}; 0156 0157 if ismember(struct1_fieldName,ignore_fieldnames) 0158 if VERBOSE 0159 warning('fieldname %s encountered, but ignored',struct1_fieldName); 0160 end 0161 continue 0162 end 0163 0164 % step through each element of the struct array, compare 0165 for sIdx = 1:nstruct_array 0166 0167 struct1_val = struct1(sIdx).(struct1_fieldName); 0168 struct2_val = struct2(sIdx).(struct1_fieldName); 0169 struct1_fieldType = class(struct1_val); 0170 struct2_fieldType = class(struct2_val); 0171 0172 %see if the fields are of the same type 0173 %if both checkTypes and checkValues are turned off, then bypass 0174 if(~strcmp(struct1_fieldType,struct2_fieldType) &&... 0175 (checkTypes || checkValues)) 0176 % If both are empty, don't worry about it 0177 if isempty(struct1_val) && isempty(struct2_val) 0178 continue 0179 end 0180 firstViolation = {struct1_fieldName}; 0181 violationReason = 'type_differs'; 0182 structsAreEqual = 0; 0183 return 0184 end 0185 0186 %if we are not comparing values and the type of field is anything 0187 %but a struct, set them to equal and move on to the next 0188 %field. Otherwise, we will compare their values in the switch statement 0189 if ~checkValues && ~isstruct(struct1_val) 0190 fieldsAreEqual = 1; 0191 continue; 0192 end 0193 0194 %for each case (except for type struct), we need to see if 0195 %(checkValues) is true. This actually seemed like the most 0196 %efficient route because a lot of the necessary logic when not comparing 0197 %values is still contained in the 'for' loop. 0198 switch struct1_fieldType 0199 0200 case numberTypes 0201 if size(struct1_val) == size(struct2_val) 0202 fieldsAreEqual = all(struct1_val(:) == struct2_val(:)); 0203 else 0204 fieldsAreEqual = 0; 0205 end 0206 0207 if ~fieldsAreEqual && ismember(struct1_fieldName, subsetIsOK) 0208 if all(ismember(struct1_val, struct2_val)) 0209 fieldsAreEqual = 1; 0210 end 0211 end 0212 0213 case 'char' 0214 fieldsAreEqual = strcmp(struct1_val,struct2_val); 0215 0216 case 'struct' 0217 [fieldsAreEqual,subFieldViolation,violationReason] = ... 0218 compare_structs(struct1_val,struct2_val,'values',checkValues,... 0219 'substruct',checkSubstruct,'types',checkTypes,... 0220 'ignore_fieldnames',ignore_fieldnames, ... 0221 'subsetIsOK', subsetIsOK); 0222 0223 case 'cell' 0224 [fieldsAreEqual,subFieldViolation,violationReason] = ... 0225 compare_cells(struct1_val,struct2_val); 0226 0227 case 'logical' 0228 if struct1_val == struct2_val 0229 fieldsAreEqual = 1; 0230 else 0231 fieldsAreEqual = 0; 0232 end 0233 0234 case 'function_handle' 0235 struct1_str = func2str(struct1_val); 0236 struct2_str = func2str(struct1_val); 0237 fieldsAreEqual = strcmp(struct1_str,struct2_str); 0238 0239 otherwise 0240 error('Fieldtype %s not supported.',struct1_fieldType); 0241 0242 0243 end % switch struct1 0244 0245 if ~fieldsAreEqual 0246 if isstruct(struct1_val) 0247 firstViolation = strcat(struct1_fieldName,'.',subFieldViolation); 0248 else 0249 firstViolation = {struct1_fieldName}; 0250 violationReason = 'values_differ'; 0251 end 0252 structsAreEqual = 0; 0253 return 0254 end % if (~fieldsAre 0255 end % for sIdx 0256 0257 end % for iField 0258 0259 % if any one field was not equal, then we would not have 0260 % made it this far. Set structsAreEqual =1 and return 0261 structsAreEqual = 1; 0262 return 0263 0264