30
30
import com .github .fge .msgsimple .bundle .MessageBundle ;
31
31
import com .github .fge .msgsimple .load .MessageBundles ;
32
32
import com .gravity9 .jsonpatch .JsonPatch ;
33
+ import com .gravity9 .jsonpatch .JsonPatchException ;
33
34
import com .gravity9 .jsonpatch .JsonPatchMessages ;
35
+ import com .gravity9 .jsonpatch .JsonPatchOperation ;
36
+ import com .gravity9 .jsonpatch .RemoveOperation ;
37
+ import com .jayway .jsonpath .PathNotFoundException ;
38
+
39
+ import javax .annotation .ParametersAreNonnullByDefault ;
34
40
import java .io .IOException ;
41
+ import java .util .ArrayList ;
35
42
import java .util .Collections ;
36
43
import java .util .HashMap ;
37
44
import java .util .HashSet ;
38
45
import java .util .Iterator ;
46
+ import java .util .List ;
39
47
import java .util .Map ;
40
48
import java .util .Set ;
41
49
import java .util .TreeSet ;
42
- import javax .annotation .ParametersAreNonnullByDefault ;
43
50
44
51
/**
45
52
* JSON "diff" implementation
@@ -96,6 +103,57 @@ public static JsonPatch asJsonPatch(final JsonNode source,
96
103
return processor .getPatch ();
97
104
}
98
105
106
+ /**
107
+ * Generate a JSON patch for transforming the source node into the target
108
+ * node ignoring given fields
109
+ *
110
+ * @param source the node to be patched
111
+ * @param target the expected result after applying the patch
112
+ * @param fieldsToIgnore list of JsonPath or JsonPointer paths which should be ignored when generating diff. Non-existing fields are ignored.
113
+ * @return the patch as a {@link JsonPatch}
114
+ * @throws JsonPatchException if fieldsToIgnored not in valid JsonPath or JsonPointer format
115
+ * @since 2.0.0
116
+ */
117
+ public static JsonPatch asJsonPatchIgnoringFields (final JsonNode source ,
118
+ final JsonNode target , final List <String > fieldsToIgnore ) throws JsonPatchException {
119
+ BUNDLE .checkNotNull (source , "common.nullArgument" );
120
+ BUNDLE .checkNotNull (target , "common.nullArgument" );
121
+ final List <JsonPatchOperation > ignoredFieldsRemoveOperations = getJsonPatchRemoveOperationsForIgnoredFields (fieldsToIgnore );
122
+
123
+ JsonNode sourceWithoutIgnoredFields = removeIgnoredFields (source , ignoredFieldsRemoveOperations );
124
+ JsonNode targetWithoutIgnoredFields = removeIgnoredFields (target , ignoredFieldsRemoveOperations );
125
+
126
+ final Map <JsonPointer , JsonNode > unchanged
127
+ = getUnchangedValues (sourceWithoutIgnoredFields , targetWithoutIgnoredFields );
128
+ final DiffProcessor processor = new DiffProcessor (unchanged );
129
+
130
+ generateDiffs (processor , JsonPointer .empty (), sourceWithoutIgnoredFields , targetWithoutIgnoredFields );
131
+ return processor .getPatch ();
132
+ }
133
+
134
+ private static JsonNode removeIgnoredFields (JsonNode node , List <JsonPatchOperation > ignoredFieldsRemoveOperations ) throws JsonPatchException {
135
+ JsonNode nodeWithoutIgnoredFields = node ;
136
+ for (JsonPatchOperation operation : ignoredFieldsRemoveOperations ) {
137
+ nodeWithoutIgnoredFields = removeIgnoredFieldOrIgnore (nodeWithoutIgnoredFields , operation );
138
+ }
139
+ return nodeWithoutIgnoredFields ;
140
+ }
141
+
142
+ private static JsonNode removeIgnoredFieldOrIgnore (JsonNode nodeWithoutIgnoredFields , JsonPatchOperation operation ) throws JsonPatchException {
143
+ try {
144
+ List <JsonPatchOperation > operationsList = new ArrayList <>();
145
+ operationsList .add (operation );
146
+ return new JsonPatch (operationsList ).apply (nodeWithoutIgnoredFields );
147
+ } catch (JsonPatchException e ) {
148
+ // If remove for specific path throws PathNotFound, it means that node does not contain specific field which should be ignored.
149
+ // See more `empty patch if object does not contain ignored field` in diff.json file.
150
+ if (e .getCause () instanceof PathNotFoundException ) {
151
+ return nodeWithoutIgnoredFields ;
152
+ }
153
+ throw e ;
154
+ }
155
+ }
156
+
99
157
/**
100
158
* Generate a JSON patch for transforming the source node into the target
101
159
* node
@@ -114,6 +172,27 @@ public static JsonNode asJson(final JsonNode source, final JsonNode target) {
114
172
}
115
173
}
116
174
175
+ /**
176
+ * Generate a JSON patch for transforming the source node into the target
177
+ * node ignoring given fields
178
+ *
179
+ * @param source the node to be patched
180
+ * @param target the expected result after applying the patch
181
+ * @param fieldsToIgnore list of JsonPath or JsonPointer paths which should be ignored when generating diff. Non-existing fields are ignored.
182
+ * @return the patch as a {@link JsonNode}
183
+ * @throws JsonPatchException if fieldsToIgnored not in valid JsonPath or JsonPointer format
184
+ * @since 2.0.0
185
+ */
186
+ public static JsonNode asJsonIgnoringFields (final JsonNode source , final JsonNode target , List <String > fieldsToIgnore ) throws JsonPatchException {
187
+ final String s ;
188
+ try {
189
+ s = MAPPER .writeValueAsString (asJsonPatchIgnoringFields (source , target , fieldsToIgnore ));
190
+ return MAPPER .readTree (s );
191
+ } catch (IOException e ) {
192
+ throw new RuntimeException ("cannot generate JSON diff" , e );
193
+ }
194
+ }
195
+
117
196
private static void generateDiffs (final DiffProcessor processor ,
118
197
final JsonPointer pointer , final JsonNode source , final JsonNode target ) {
119
198
if (EQUIVALENCE .equivalent (source , target ))
@@ -157,24 +236,24 @@ private static void generateObjectDiffs(final DiffProcessor processor,
157
236
final JsonPointer pointer , final ObjectNode source ,
158
237
final ObjectNode target ) {
159
238
final Set <String > firstFields
160
- = collect (source .fieldNames (), new TreeSet <String >());
239
+ = collect (source .fieldNames (), new TreeSet <>());
161
240
final Set <String > secondFields
162
- = collect (target .fieldNames (), new TreeSet <String >());
241
+ = collect (target .fieldNames (), new TreeSet <>());
163
242
164
- final Set <String > copy1 = new HashSet <String >(firstFields );
243
+ final Set <String > copy1 = new HashSet <>(firstFields );
165
244
copy1 .removeAll (secondFields );
166
245
167
246
for (final String field : Collections .unmodifiableSet (copy1 ))
168
247
processor .valueRemoved (pointer .append (field ), source .get (field ));
169
248
170
- final Set <String > copy2 = new HashSet <String >(secondFields );
249
+ final Set <String > copy2 = new HashSet <>(secondFields );
171
250
copy2 .removeAll (firstFields );
172
251
173
252
174
253
for (final String field : Collections .unmodifiableSet (copy2 ))
175
254
processor .valueAdded (pointer .append (field ), target .get (field ));
176
255
177
- final Set <String > intersection = new HashSet <String >(firstFields );
256
+ final Set <String > intersection = new HashSet <>(firstFields );
178
257
intersection .retainAll (secondFields );
179
258
180
259
for (final String field : intersection )
@@ -222,7 +301,7 @@ private static void generateArrayDiffs(final DiffProcessor processor,
222
301
223
302
static Map <JsonPointer , JsonNode > getUnchangedValues (final JsonNode source ,
224
303
final JsonNode target ) {
225
- final Map <JsonPointer , JsonNode > ret = new HashMap <JsonPointer , JsonNode >();
304
+ final Map <JsonPointer , JsonNode > ret = new HashMap <>();
226
305
computeUnchanged (ret , JsonPointer .empty (), source , target );
227
306
return ret ;
228
307
}
@@ -278,4 +357,13 @@ private static void computeArray(final Map<JsonPointer, JsonNode> ret,
278
357
computeUnchanged (ret , pointer .append (i ), source .get (i ),
279
358
target .get (i ));
280
359
}
360
+
361
+ private static List <JsonPatchOperation > getJsonPatchRemoveOperationsForIgnoredFields (List <String > fieldsToIgnore ) {
362
+ final List <JsonPatchOperation > ignoredFieldsRemoveOperations = new ArrayList <>();
363
+ for (String fieldToIgnore : fieldsToIgnore ) {
364
+ ignoredFieldsRemoveOperations .add (new RemoveOperation (fieldToIgnore ));
365
+ }
366
+ return ignoredFieldsRemoveOperations ;
367
+ }
368
+
281
369
}
0 commit comments