All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.fge.jackson.JsonNumEquals Maven / Gradle / Ivy

There is a newer version: 2.0
Show newest version
/*
 * Copyright (c) 2014, Francis Galiegue ([email protected])
 *
 * This software is dual-licensed under:
 *
 * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
 *   later version;
 * - the Apache Software License (ASL) version 2.0.
 *
 * The text of this file and of both licenses is available at the root of this
 * project or, if you have the jar distribution, in directory META-INF/, under
 * the names LGPL-3.0.txt and ASL-2.0.txt respectively.
 *
 * Direct link to the sources:
 *
 * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
 * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
 */

package com.github.fge.jackson;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Equivalence;
import com.google.common.collect.Sets;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * An {@link Equivalence} strategy for JSON Schema equality
 *
 * 

{@link JsonNode} does a pretty good job of obeying the {@link * Object#equals(Object) equals()}/{@link Object#hashCode() hashCode()} * contract. And in fact, it does it too well for JSON Schema.

* *

For instance, it considers numeric nodes {@code 1} and {@code 1.0} to be * different nodes, which is true. But some IETF RFCs and drafts (among them, * JSON Schema and JSON Patch) mandate that numeric JSON values be considered * equal if their mathematical value is the same. This class implements this * kind of equality.

*/ public final class JsonNumEquals extends Equivalence { private static final Equivalence INSTANCE = new JsonNumEquals(); private JsonNumEquals() { } public static Equivalence getInstance() { return INSTANCE; } @Override protected boolean doEquivalent(final JsonNode a, final JsonNode b) { /* * If both are numbers, delegate to the helper method */ if (a.isNumber() && b.isNumber()) return numEquals(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 int doHash(final JsonNode t) { /* * If this is a numeric node, we want the same hashcode for the same * mathematical values. Go with double, its range is good enough for * 99+% of use cases. */ if (t.isNumber()) return Double.valueOf(t.doubleValue()).hashCode(); /* * 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> iterator = t.fields(); Map.Entry entry; while (iterator.hasNext()) { entry = iterator.next(); ret = 31 * ret + (entry.getKey().hashCode() ^ doHash(entry.getValue())); } return ret; } private static boolean numEquals(final JsonNode a, final JsonNode b) { /* * If both numbers are integers, delegate to JsonNode. */ if (a.isIntegralNumber() && b.isIntegralNumber()) return a.equals(b); /* * Otherwise, compare decimal values. */ return a.decimalValue().compareTo(b.decimalValue()) == 0; } 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 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 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy