Skip to content

JsonNumEquals fails to compare IntNode and LongNode with same value #45

Description

@grimsa

Problem

JsonNode intNode = IntNode.valueOf(1);
JsonNode longNode = LongNode.valueOf(1);
boolean same = JsonNumEquals.getInstance().equivalent(intNode, longNode);
// at this point same == false

Cause
In com.github.fge.jackson.JsonNumEquals#numEquals:

private static boolean numEquals(final JsonNode a, final JsonNode b)
{
    /*
     * If both numbers are integers, delegate to JsonNode.
     */
    if (a.isIntegralNumber() && b.isIntegralNumber())     // this is true
        return a.equals(b);           // yet this is false

    /*
     * Otherwise, compare decimal values.
     */
    return a.decimalValue().compareTo(b.decimalValue()) == 0;
}

Activity

  1. grimsa commented on Jul 31, 2021

    @grimsa
    Author

    Workaround

    Since Jackson 2.6.0 there is JsonNode#equals(Comparator<JsonNode>, JsonNode) method [1].

    The numeric equality comparator can be implemented like this:

    class NumericEqualityComparator implements Comparator<JsonNode> {
        NumericEqualityComparator() {
        }
    
        @Override
        public int compare(JsonNode a, JsonNode b) {
            return areNodesNumericallyEqual(a, b)
                    ? 0
                    : 1;
        }
    
        private boolean areNodesNumericallyEqual(JsonNode a, JsonNode b) {
            return a.isNumber() && b.isNumber()
                    ? areNumberNodesNumericallyEqual(a, b)
                    : a.equals(b);
        }
    
        private boolean areNumberNodesNumericallyEqual(JsonNode a, JsonNode b) {
            if (a.isIntegralNumber() && b.isIntegralNumber()) {
                return a.canConvertToLong() && b.canConvertToLong()
                        ? a.longValue() == b.longValue()                        // Comparing as long to avoid BigInteger allocations
                        : a.bigIntegerValue().equals(b.bigIntegerValue());
            }
            return a.isBigDecimal() || b.isBigDecimal()
                    ? a.decimalValue().compareTo(b.decimalValue()) == 0
                    : Double.compare(a.doubleValue(), b.doubleValue()) == 0;    // Comparing as double to avoid BigDecimal allocations
        }
    }
    

    And then JsonNode comparison is performed like this node.equals(new NumericEqualityComparator(), otherNode)

    [1] FasterXML/jackson-databind#790

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions