我目前正在使用Java的Jackson JSON库来比较一些JSON对象。
JSON对象包含一些数学计算的结果。例如:
{
name:"result_set_1"
result:[0.123151351353,1.0123151533,2.0123051353]
}
将这些与参考实现进行比较时,我注意到某些浏览器产生的结果略有不同。这很好,但我需要以某种方式确保在比较数字时我有宽容度。
JSONNode.equals()实际上做了一个非常好的深度等于,但它以一种强制它们完全相等的方式比较Numbers。我需要增加容忍度。
有没有办法与容忍度进行深度平等?
现在我发现的唯一方法是迭代每个节点,检查它是否是一个数字,并进行容差检查而不是等于。但是这个方法非常笨重,因为你必须检查节点是一个对象,一个数组,一个字符串......等等......并为每个节点做特定的事情。我只想要数字的自定义行为。
有更优雅的方式吗?任何第三方图书馆?
答案 0 :(得分:0)
前言:使用ObjectMapper
配置您的DeserializationFeature.USE_BIGDECIMAL_FOR_FLOATS
;默认情况下,杰克逊会将“非整数”JSON数字反序列化为double
,但如果这样做,则会失去精确度。
以下是我编写的one Equivalence<JsonNode>
改编的例子;您必须覆盖doNumEquivalent()
和doNumHash()
才能满足您的需求。这意味着你使用番石榴,但实际上,你应该这样做。
注意,是的,有一个哈希;您可能需要也可能不需要它,但这样做并没有什么坏处,特别是因为这意味着您将能够使用Set<Equivalence.Wrapper<JsonNode>>
(您必须.add(myEquivalence.wrap(myNode))
。
注2:NodeType
是我的特定类别;对于“核心杰克逊”,您希望使用.getNodeType()
代替JsonNode
。
用法:if (myEquivalence.equivalent(a, b)) // etc etc
public abstract class JsonNumEquivalence
extends Equivalence<JsonNode>
{
// Implement!
protected abstract boolean doNumEquivalent(final JsonNode a, final JsonNode b);
// Implement!
protected abstract int doNumHash(final JsonNode t);
@Override
protected final boolean doEquivalent(final JsonNode a, final JsonNode b)
{
/*
* If both are numbers, delegate to the helper method
*/
if (a.isNumber() && b.isNumber())
return doNumEquivalent(a, b);
final NodeType typeA = NodeType.getNodeType(a);
final NodeType typeB = NodeType.getNodeType(b);
/*
* If they are of different types, no dice
*/
if (typeA != typeB)
return false;
/*
* For all other primitive types than numbers, trust JsonNode
*/
if (!a.isContainerNode())
return a.equals(b);
/*
* OK, so they are containers (either both arrays or objects due to the
* test on types above). They are obviously not equal if they do not
* have the same number of elements/members.
*/
if (a.size() != b.size())
return false;
/*
* Delegate to the appropriate method according to their type.
*/
return typeA == NodeType.ARRAY ? arrayEquals(a, b) : objectEquals(a, b);
}
@Override
protected final int doHash(final JsonNode t)
{
/*
* If this is a numeric node, delegate to the helper method
*/
if (t.isNumber())
return doNumHash(t);
/*
* If this is a primitive type (other than numbers, handled above),
* delegate to JsonNode.
*/
if (!t.isContainerNode())
return t.hashCode();
/*
* The following hash calculations work, yes, but they are poor at best.
* And probably slow, too.
*
* TODO: try and figure out those hash classes from Guava
*/
int ret = 0;
/*
* If the container is empty, just return
*/
if (t.size() == 0)
return ret;
/*
* Array
*/
if (t.isArray()) {
for (final JsonNode element: t)
ret = 31 * ret + doHash(element);
return ret;
}
/*
* Not an array? An object.
*/
final Iterator<Map.Entry<String, JsonNode>> iterator = t.fields();
Map.Entry<String, JsonNode> entry;
while (iterator.hasNext()) {
entry = iterator.next();
ret = 31 * ret
+ (entry.getKey().hashCode() ^ doHash(entry.getValue()));
}
return ret;
}
private boolean arrayEquals(final JsonNode a, final JsonNode b)
{
/*
* We are guaranteed here that arrays are the same size.
*/
final int size = a.size();
for (int i = 0; i < size; i++)
if (!doEquivalent(a.get(i), b.get(i)))
return false;
return true;
}
private boolean objectEquals(final JsonNode a, final JsonNode b)
{
/*
* Grab the key set from the first node
*/
final Set<String> keys = Sets.newHashSet(a.fieldNames());
/*
* Grab the key set from the second node, and see if both sets are the
* same. If not, objects are not equal, no need to check for children.
*/
final Set<String> set = Sets.newHashSet(b.fieldNames());
if (!set.equals(keys))
return false;
/*
* Test each member individually.
*/
for (final String key: keys)
if (!doEquivalent(a.get(key), b.get(key)))
return false;
return true;
}
}