Как я могу сделать "глубокое сравнение" или "diff" на двух структурах?
(Это вопрос холодного синтеза)
У меня есть две разные структуры, которые могут содержать или не содержать одни и те же данные, и я хочу видеть, есть ли они! Мои структуры всегда будут содержать простые значения (числа, строки или логические значения), потому что они создаются с помощью DeserializeJSON, так что, надеюсь, это можно сделать легко.
Я нашел пост Бена Наделя здесь, но эта техника, похоже, не работает для меня. Вот что я попробовал до сих пор (там есть некоторый код cfwheels):
itemA = DeSerializeJSON(model("itemsnapshot").findByKey(4).json);
itemB = DeSerializeJSON(model("itemsnapshot").findByKey(5).json);
StructDelete(itemA,"updatedAt");
StructDelete(itemB,"updatedAt");
StructDelete(itemA,"createdAt");
StructDelete(itemB,"createdAt");
writedump(itemA);
writedump(itemB);
out = itemA.Equals(itemB);
writedump(out);
И результаты этого выглядят так:
Struct
code string C112
companyid number 1
cost number 5000
deletedAt string
description string Nightstand
id number 70634
itemtypeid string 13
projectid number 8
unittypeid string
Struct
code string C112
companyid number 1
cost number 5000
deletedAt string
description string Nightstand
id number 70634
itemtypeid string 13
projectid number 8
unittypeid string
boolean false
поэтому, как вы увидите выше, хотя данные внутри Structs, похоже, точно совпадают, они не проходят тест Equals().
Кто-нибудь еще сделал это успешно?
6 ответов
Вот решение Бена, быстро адаптированное к моим потребностям, вы можете настроить его дальше (и, надеюсь, сделать его более привлекательным):
<cffunction name="DiffStructs" hint="Compute the differences between two structures" access="public" output="true" returntype="array" >
<cfargument name="First" type="struct" required="true" />
<cfargument name="Second" type="struct" required="true" />
<cfargument name="ignoreMissing" type="boolean" required="false" default="false" />
<cfargument name="ignoreFirstEmptyString" type="boolean" required="false" default="false" />
<cfargument name="ignoreSecondEmptyString" type="boolean" required="false" default="false" />
<cfset var Result = arrayNew(1) >
<cfset var Keys = structNew() >
<cfset var KeyName = "" >
<cfset var obj = "" >
<cfset var firstOk = true >
<cfset var secondOk = true >
<cfloop collection="#Arguments.First#" item="KeyName">
<cfset Keys[KeyName]=1>
</cfloop>
<cfloop collection="#Arguments.Second#" item="KeyName">
<cfset Keys[KeyName]=1>
</cfloop>
<cfloop collection="#Keys#" item="KeyName">
<cfif NOT StructKeyExists(Arguments.First, KeyName) >
<cfif NOT arguments.ignoreMissing>
<cfif structFind(Arguments.Second, KeyName) neq "">
<cfif arguments.ignoreSecondEmptyString>
<cfset obj = { key = KeyName
,old = ""
,new = structFind(Arguments.Second, KeyName) } >
<cfset arrayAppend(Result, obj )>
</cfif>
</cfif>
</cfif>
<cfelseif NOT StructKeyExists(Arguments.Second, KeyName)>
<cfif NOT arguments.ignoreMissing>
<cfif structFind(Arguments.First, KeyName) neq "">
<cfif arguments.ignoreFirstEmptyString >
<cfset obj = { key = KeyName
,old = structFind(Arguments.First, KeyName)
,new = "" } >
<cfset arrayAppend(Result, obj )>
</cfif>
</cfif>
</cfif>
<cfelseif Arguments.First[KeyName] NEQ Arguments.Second[KeyName] >
<cfset firstOk = true >
<cfset secondOk = true >
<cfif structFind(Arguments.Second, KeyName) eq "">
<cfif arguments.ignoreSecondEmptyString>
<cfset firstOk = false >
</cfif>
</cfif>
<cfif structFind(Arguments.First, KeyName) eq "">
<cfif arguments.ignoreFirstEmptyString>
<cfset secondOk = false >
</cfif>
</cfif>
<cfif firstOk AND secondOk >
<cfset obj = { key = KeyName
,old = structFind(Arguments.First, KeyName)
,new = structFind(Arguments.Second, KeyName) } >
<cfset arrayAppend(Result, obj )>
</cfif>
</cfif>
</cfloop>
<cfreturn Result>
</cffunction>
Если вы используете CF9 или Railo 3
ArrayContains([struct1], struct2); //case-sensitive
или же
ArrayFindNoCase([struct1], struct2)); //case-insensitive, 0 if not the same.
ArrayContainsNoCase([struct1], struct2); // if you use Railo
В Coldfusion Structures скрыт небольшой удобный метод hashCode(). Хотя имейте ввиду, что это недокументировано.
<cfif struct1.hashCode() Eq struct2.hashCode()>
</cfif>
Вы также можете выполнить это, используя собственный Java-метод, унаследованный CFC.
isThisTrue = ObjA.equals(ObjB);
Вот то, что я быстро собрал. У него есть параметр, чтобы определить, следует ли выполнять сравнение значений и ключей с учетом регистра. Кинь эти две функции (StructEquals()
, ArrayEquals()
) в каких то утилитах ХФУ.
Ограничение: не работает для структур / массивов, содержащих запросы или объекты.
<cffunction name="StructEquals" access="public" returntype="boolean" output="false"
hint="Returns whether two structures are equal, going deep.">
<cfargument name="stc1" type="struct" required="true" hint="First struct to be compared." />
<cfargument name="stc2" type="struct" required="true" hint="Second struct to be compared." />
<cfargument name="blnCaseSensitive" type="boolean" required="false" default="false" hint="Whether or not values are compared case-sensitive." />
<cfargument name="blnCaseSensitiveKeys" type="boolean" required="false" default="false" hint="Whether or not structure keys are compared case-sensitive." />
<cfscript>
if(StructCount(stc1) != StructCount(stc2))
return false;
var arrKeys1 = StructKeyArray(stc1);
var arrKeys2 = StructKeyArray(stc2);
ArraySort(arrKeys1, 'text');
ArraySort(arrKeys2, 'text');
if(!ArrayEquals(arrKeys1, arrKeys2, blnCaseSensitiveKeys, blnCaseSensitiveKeys))
return false;
for(var i = 1; i <= ArrayLen(arrKeys1); i++) {
var strKey = arrKeys1[i];
if(IsStruct(stc1[strKey])) {
if(!IsStruct(stc2[strKey]))
return false;
if(!StructEquals(stc1[strKey], stc2[strKey], blnCaseSensitive, blnCaseSensitiveKeys))
return false;
}
else if(IsArray(stc1[strKey])) {
if(!IsArray(stc2[strKey]))
return false;
if(!ArrayEquals(stc1[strKey], stc2[strKey], blnCaseSensitive, blnCaseSensitiveKeys))
return false;
}
else if(IsSimpleValue(stc1[strKey]) && IsSimpleValue(stc2[strKey])) {
if(blnCaseSensitive) {
if(Compare(stc1[strKey], stc2[strKey]) != 0)
return false;
}
else {
if(CompareNoCase(stc1[strKey], stc2[strKey]) != 0)
return false;
}
}
else {
throw("Can only compare structures, arrays, and simple values. No queries or complex objects.");
}
}
return true;
</cfscript>
</cffunction>
<cffunction name="ArrayEquals" access="public" returntype="boolean" output="false"
hint="Returns whether two arrays are equal, including deep comparison if the arrays contain structures or sub-arrays.">
<cfargument name="arr1" type="array" required="true" hint="First struct to be compared." />
<cfargument name="arr2" type="array" required="true" hint="Second struct to be compared." />
<cfargument name="blnCaseSensitive" type="boolean" required="false" default="false" hint="Whether or not values are compared case-sensitive." />
<cfargument name="blnCaseSensitiveKeys" type="boolean" required="false" default="false" hint="Whether or not structure keys are compared case-sensitive, if array contains structures." />
<cfscript>
if(ArrayLen(arr1) != ArrayLen(arr2))
return false;
for(var i = 1; i <= ArrayLen(arr1); i++) {
if(IsStruct(arr1[i])) {
if(!IsStruct(arr2[i]))
return false;
if(!StructEquals(arr1[i], arr2[i], blnCaseSensitive, blnCaseSensitiveKeys))
return false;
}
else if(IsArray(arr1[i])) {
if(!IsArray(arr2[i]))
return false;
if(!ArrayEquals(arr1[i], arr2[i], blnCaseSensitive, blnCaseSensitiveKeys))
return false;
}
else if(IsSimpleValue(arr1[i]) && IsSimpleValue(arr2[i])) {
if(blnCaseSensitive) {
if(Compare(arr1[i], arr2[i]) != 0)
return false;
}
else {
if(CompareNoCase(arr1[i], arr2[i]) != 0)
return false;
}
}
else {
throw("Can only compare structures, arrays, and simple values. No queries or complex objects.");
}
}
return true;
</cfscript>
</cffunction>
Модульные тесты для всех, кто заинтересован:
public void function test_StructEquals() {
AssertTrue(utils.StructEquals({}, StructNew()));
AssertTrue(utils.StructEquals({}, StructNew(), true, true));
AssertFalse(utils.StructEquals({}, {"a": "b", "c": "d"}));
AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"C": "D", "A": "B"}));
AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"C": "D", "A": "B"}, true, false));
AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"C": "D", "A": "B"}, false, true));
AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"C": "d", "A": "b"}));
AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"C": "d", "A": "b"}, true, false));
AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"C": "d", "A": "b"}, false, true));
AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"c": "D", "a": "B"}));
AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"c": "D", "a": "B"}, true, false));
AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"c": "D", "a": "B"}, false, true));
var stc1 = {
"test": {
"hello": "world",
"goodbye": "space",
"somearr": [
{ "a": 1, "b": 2 },
"WORD",
[
{ "x": 97, "y": 98, "z": 99 },
{ "i": 50, "j": 51, "k": 52 }
]
]
}
};
var stc2 = {
"test": {
"goodbye": "space",
"hello": "world",
"somearr": [
{ "a": 1, "b": 2 },
"WORD",
[
{ "z": 99, "x": 97, "y": 98 },
{ "i": 50, "k": 52, "j": 51 }
]
]
}
};
AssertTrue(utils.StructEquals(stc1, stc2, true, true));
stc2.test.somearr[2] = "WOrD";
AssertTrue(utils.StructEquals(stc1, stc2));
AssertTrue(utils.StructEquals(stc1, stc2, false, true));
AssertFalse(utils.StructEquals(stc1, stc2, true, false));
stc2.test.somearr[3][1] = { "z": 99, "X": 97, "y": 98 };
AssertTrue(utils.StructEquals(stc1, stc2));
AssertFalse(utils.StructEquals(stc1, stc2, false, true));
AssertFalse(utils.StructEquals(stc1, stc2, true, false));
stc2.test.somearr[2] = "WORD";
AssertTrue(utils.StructEquals(stc1, stc2));
AssertFalse(utils.StructEquals(stc1, stc2, false, true));
AssertTrue(utils.StructEquals(stc1, stc2, true, false));
}
public void function test_ArrayEquals() {
AssertTrue(utils.ArrayEquals([], ArrayNew(1)));
AssertTrue(utils.ArrayEquals([], ArrayNew(1), true, true));
AssertFalse(utils.ArrayEquals([], [1, 2, 3]));
AssertTrue(utils.ArrayEquals(['a', 'b', 'c'], ['A', 'B', 'C']));
AssertFalse(utils.ArrayEquals(['a', 'b', 'c'], ['A', 'B', 'C'], true, false));
AssertTrue(utils.ArrayEquals(['a', 'b', 'c'], ['A', 'B', 'C'], false, true));
AssertFalse(utils.ArrayEquals(['a', 'b', 'c'], ['a', 'c', 'b']));
AssertTrue(utils.ArrayEquals([1, 2, 3], [1, 2, 3]));
AssertFalse(utils.ArrayEquals([1, 2, 3], [1, 3, 2]));
}
if(serializeJSON(itemA) eq serializeJSON(itemB))
//They match!
else
//They don't!
Вы уже используете JSON для манипулирования ими, вам следует просто придерживаться этого. Не то, чтобы кого-то это волновало десять лет спустя, но этот метод работает для меня.