您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

JsonComparison.java 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.test;
  21. import java.text.ParseException;
  22. import java.text.SimpleDateFormat;
  23. import java.util.ArrayList;
  24. import java.util.Collections;
  25. import java.util.Date;
  26. import java.util.HashSet;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Set;
  30. import javax.annotation.CheckForNull;
  31. import javax.annotation.Nullable;
  32. import javax.annotation.concurrent.ThreadSafe;
  33. import org.json.simple.JSONArray;
  34. import org.json.simple.JSONObject;
  35. import org.json.simple.parser.JSONParser;
  36. import static java.util.Collections.synchronizedSet;
  37. @ThreadSafe
  38. class JsonComparison {
  39. private boolean strictTimezone = false;
  40. private boolean strictArrayOrder = false;
  41. private Set<String> ignoredFields = synchronizedSet(new HashSet<>());
  42. boolean isStrictTimezone() {
  43. return strictTimezone;
  44. }
  45. JsonComparison withTimezone() {
  46. this.strictTimezone = true;
  47. return this;
  48. }
  49. boolean isStrictArrayOrder() {
  50. return strictArrayOrder;
  51. }
  52. JsonComparison withStrictArrayOrder() {
  53. this.strictArrayOrder = true;
  54. return this;
  55. }
  56. JsonComparison setIgnoredFields(String... ignoredFields) {
  57. Collections.addAll(this.ignoredFields, ignoredFields);
  58. return this;
  59. }
  60. boolean areSimilar(String expected, String actual) {
  61. Object expectedJson = parse(expected);
  62. Object actualJson = parse(actual);
  63. return compare(expectedJson, actualJson);
  64. }
  65. private Object parse(String s) {
  66. try {
  67. JSONParser parser = new JSONParser();
  68. return parser.parse(s);
  69. } catch (Exception e) {
  70. throw new IllegalStateException("Invalid JSON: " + s, e);
  71. }
  72. }
  73. private boolean compare(@Nullable Object expectedObject, @Nullable Object actualObject) {
  74. if (expectedObject == null) {
  75. return actualObject == null;
  76. }
  77. if (actualObject == null) {
  78. // expected non-null, got null
  79. return false;
  80. }
  81. if (expectedObject.getClass() != actualObject.getClass()) {
  82. return false;
  83. }
  84. if (expectedObject instanceof JSONArray) {
  85. return compareArrays((JSONArray) expectedObject, (JSONArray) actualObject);
  86. }
  87. if (expectedObject instanceof JSONObject) {
  88. return compareObjects((JSONObject) expectedObject, (JSONObject) actualObject);
  89. }
  90. if (expectedObject instanceof String) {
  91. return compareStrings((String) expectedObject, (String) actualObject);
  92. }
  93. if (expectedObject instanceof Number) {
  94. return compareNumbers((Number) expectedObject, (Number) actualObject);
  95. }
  96. return compareBooleans((Boolean) expectedObject, (Boolean) actualObject);
  97. }
  98. private boolean compareBooleans(Boolean expected, Boolean actual) {
  99. return expected.equals(actual);
  100. }
  101. private boolean compareNumbers(Number expected, Number actual) {
  102. double d1 = expected.doubleValue();
  103. double d2 = actual.doubleValue();
  104. if (Double.compare(d1, d2) == 0) {
  105. return true;
  106. }
  107. return Math.abs(d1 - d2) <= 0.0000001;
  108. }
  109. private boolean compareStrings(String expected, String actual) {
  110. if (!strictTimezone) {
  111. // two instants with different timezones are considered as identical (2015-01-01T13:00:00+0100 and 2015-01-01T12:00:00+0000)
  112. Date expectedDate = tryParseDate(expected);
  113. Date actualDate = tryParseDate(actual);
  114. if (expectedDate != null && actualDate != null) {
  115. return expectedDate.getTime() == actualDate.getTime();
  116. }
  117. }
  118. return expected.equals(actual);
  119. }
  120. private boolean compareArrays(JSONArray expected, JSONArray actual) {
  121. if (strictArrayOrder) {
  122. return compareArraysByStrictOrder(expected, actual);
  123. }
  124. return compareArraysByLenientOrder(expected, actual);
  125. }
  126. private boolean compareArraysByStrictOrder(JSONArray expected, JSONArray actual) {
  127. if (expected.size() != actual.size()) {
  128. return false;
  129. }
  130. for (int index = 0; index < expected.size(); index++) {
  131. Object expectedElt = expected.get(index);
  132. Object actualElt = actual.get(index);
  133. if (!compare(expectedElt, actualElt)) {
  134. return false;
  135. }
  136. }
  137. return true;
  138. }
  139. private boolean compareArraysByLenientOrder(JSONArray expected, JSONArray actual) {
  140. if (expected.size() > actual.size()) {
  141. return false;
  142. }
  143. List remainingActual = new ArrayList(actual);
  144. for (Object expectedElement : expected) {
  145. // element can be null
  146. boolean found = false;
  147. for (Object actualElement : remainingActual) {
  148. if (compare(expectedElement, actualElement)) {
  149. found = true;
  150. remainingActual.remove(actualElement);
  151. break;
  152. }
  153. }
  154. if (!found) {
  155. return false;
  156. }
  157. }
  158. return remainingActual.isEmpty();
  159. }
  160. private boolean compareObjects(JSONObject expectedMap, JSONObject actualMap) {
  161. // each key-value of expected map must exist in actual map
  162. for (Map.Entry<Object, Object> expectedEntry : (Set<Map.Entry<Object, Object>>) expectedMap.entrySet()) {
  163. Object key = expectedEntry.getKey();
  164. if (shouldIgnoreField(key)) {
  165. continue;
  166. }
  167. if (!actualMap.containsKey(key)) {
  168. return false;
  169. }
  170. if (!compare(expectedEntry.getValue(), actualMap.get(key))) {
  171. return false;
  172. }
  173. }
  174. return true;
  175. }
  176. private boolean shouldIgnoreField(Object key) {
  177. return key instanceof String && ignoredFields.contains(key);
  178. }
  179. @CheckForNull
  180. private static Date tryParseDate(String s) {
  181. try {
  182. return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
  183. } catch (ParseException ignored) {
  184. // not a datetime
  185. return null;
  186. }
  187. }
  188. }