比较具有数字容差的两个JSON对象

时间:2014-05-02 10:01:14

标签: java jackson equals

我目前正在使用Java的Jackson JSON库来比较一些JSON对象。

JSON对象包含一些数学计算的结果。例如:

{
  name:"result_set_1"
  result:[0.123151351353,1.0123151533,2.0123051353]
}

将这些与参考实现进行比较时,我注意到某些浏览器产生的结果略有不同。这很好,但我需要以某种方式确保在比较数字时我有宽容度。

JSONNode.equals()实际上做了一个非常好的深度等于,但它以一种强制它们完全相等的方式比较Numbers。我需要增加容忍度。

有没有办法与容忍度进行深度平等?

现在我发现的唯一方法是迭代每个节点,检查它是否是一个数字,并进行容差检查而不是等于。但是这个方法非常笨重,因为你必须检查节点是一个对象,一个数组,一个字符串......等等......并为每个节点做特定的事情。我只想要数字的自定义行为。

有更优雅的方式吗?任何第三方图书馆?

1 个答案:

答案 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;
    }
}