From 27fdfd68eb92dd927f38c39c71680a620976d43f Mon Sep 17 00:00:00 2001 From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 23 Sep 2020 12:55:24 -0700 Subject: [PATCH] Update JSON Patch Functionality (#15430) * Release azure-core-experimental 1.0.0-beta.5 * Changed value to Object instead of JSON string, added getter for JSON Patch operations contained in the document * Fix linting issues * Update JsonPatchDocument to accept JsonSerializer during construction, added support for null in add, replace, and test operations * Additional changes for JSON Patch support * Fix possible exception in toString and suppress Checkstyles for having external dependencies in public API --- .../checkstyle/checkstyle-suppressions.xml | 4 + sdk/core/azure-core-experimental/CHANGELOG.md | 1 + .../jsonpatch/JsonPatchDocument.java | 134 ++++++++++-------- .../JsonPatchDocumentSerializer.java | 50 +++++++ .../jsonpatch/JsonPatchOperation.java | 118 +++++++++++---- .../jsonpatch/JsonPatchOperationKind.java | 27 +++- .../JsonPatchOperationSerializer.java | 62 ++++++++ .../src/main/java/module-info.java | 2 + .../JsonPatchDocumentJavaDocCodeSnippet.java | 40 +++--- .../jsonpatch/JsonPatchDocumentTests.java | 66 +++++---- 10 files changed, 370 insertions(+), 134 deletions(-) create mode 100644 sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentSerializer.java create mode 100644 sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationSerializer.java diff --git a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml index 6b611791a0d6..8a1a8e581ba9 100755 --- a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml +++ b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml @@ -572,4 +572,8 @@ + + + diff --git a/sdk/core/azure-core-experimental/CHANGELOG.md b/sdk/core/azure-core-experimental/CHANGELOG.md index 492fdfb9f883..73653166923f 100644 --- a/sdk/core/azure-core-experimental/CHANGELOG.md +++ b/sdk/core/azure-core-experimental/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.0.0-beta.5 (Unreleased) +- Added `JsonPatchDocument` to support JSON Patch functionality. ## 1.0.0-beta.4 (2020-09-08) diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocument.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocument.java index 7f35df9151cc..9492bbc3e0a6 100644 --- a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocument.java +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocument.java @@ -5,15 +5,17 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.serializer.JacksonAdapter; -import com.fasterxml.jackson.core.JsonGenerator; +import com.azure.core.util.serializer.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; /** * Represents a JSON Patch document. @@ -25,12 +27,34 @@ public class JsonPatchDocument { private final ClientLogger logger = new ClientLogger(JsonPatchDocument.class); private final List operations; + private final JsonSerializer serializer; /** * Creates a new JSON Patch document. */ public JsonPatchDocument() { + this(null); + } + + /** + * Creates a new JSON Patch document. + *

+ * If {@code serializer} isn't specified {@link JacksonAdapter} will be used. + * + * @param serializer The {@link JsonSerializer} that will be used to serialize patch operation values. + */ + public JsonPatchDocument(JsonSerializer serializer) { this.operations = new ArrayList<>(); + this.serializer = serializer; + } + + /** + * Gets an unmodifiable list of JSON Patch operations in this document. + * + * @return An unmodifiable list of JSON Patch operations in this document. + */ + public List getJsonPatchOperations() { + return Collections.unmodifiableList(operations); } /** @@ -43,19 +67,17 @@ public JsonPatchDocument() { * *

Code Samples

* - * {@codesnippet com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendAdd#String-String} + * {@codesnippet com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendAdd#String-Object} * * @param path The path to apply the addition. - * @param rawJsonValue The raw JSON value to add to the path. + * @param value The value to add to the path. * @return The updated JsonPatchDocument object. - * @throws NullPointerException If {@code path} or {@code rawJsonValue} is null. + * @throws NullPointerException If {@code path} is null. */ - public JsonPatchDocument appendAdd(String path, String rawJsonValue) { + public JsonPatchDocument appendAdd(String path, Object value) { Objects.requireNonNull(path, "'path' cannot be null."); - Objects.requireNonNull(rawJsonValue, "'rawJsonValue' cannot be null."); - operations.add(new JsonPatchOperation(JsonPatchOperationKind.ADD, path, null, rawJsonValue)); - return this; + return appendOperation(JsonPatchOperationKind.ADD, null, path, serializeValue(value)); } /** @@ -65,19 +87,17 @@ public JsonPatchDocument appendAdd(String path, String rawJsonValue) { * *

Code Samples

* - * {@codesnippet com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendReplace#String-String} + * {@codesnippet com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendReplace#String-Object} * * @param path The path to replace. - * @param rawJsonValue The raw JSON value to use as the replacement. + * @param value The value to use as the replacement. * @return The updated JsonPatchDocument object. - * @throws NullPointerException If {@code path} or {@code rawJsonValue} is null. + * @throws NullPointerException If {@code path} is null. */ - public JsonPatchDocument appendReplace(String path, String rawJsonValue) { + public JsonPatchDocument appendReplace(String path, Object value) { Objects.requireNonNull(path, "'path' cannot be null."); - Objects.requireNonNull(rawJsonValue, "'rawJsonValue' cannot be null."); - operations.add(new JsonPatchOperation(JsonPatchOperationKind.REPLACE, path, null, rawJsonValue)); - return this; + return appendOperation(JsonPatchOperationKind.REPLACE, null, path, serializeValue(value)); } /** @@ -98,8 +118,7 @@ public JsonPatchDocument appendCopy(String from, String path) { Objects.requireNonNull(from, "'from' cannot be null."); Objects.requireNonNull(path, "'path' cannot be null."); - operations.add(new JsonPatchOperation(JsonPatchOperationKind.COPY, path, from, null)); - return this; + return appendOperation(JsonPatchOperationKind.COPY, from, path, null); } /** @@ -122,8 +141,7 @@ public JsonPatchDocument appendMove(String from, String path) { Objects.requireNonNull(from, "'from' cannot be null."); Objects.requireNonNull(path, "'path' cannot be null."); - operations.add(new JsonPatchOperation(JsonPatchOperationKind.MOVE, path, from, null)); - return this; + return appendOperation(JsonPatchOperationKind.MOVE, from, path, null); } /** @@ -142,8 +160,7 @@ public JsonPatchDocument appendMove(String from, String path) { public JsonPatchDocument appendRemove(String path) { Objects.requireNonNull(path, "'path' cannot be null."); - operations.add(new JsonPatchOperation(JsonPatchOperationKind.REMOVE, path, null, null)); - return this; + return appendOperation(JsonPatchOperationKind.REMOVE, null, path, null); } /** @@ -153,18 +170,43 @@ public JsonPatchDocument appendRemove(String path) { * *

Code Samples

* - * {@codesnippet com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendTest#String-String} + * {@codesnippet com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendTest#String-Object} * * @param path The path to test. - * @param rawJsonValue The raw JSON value to test against. + * @param value The value to test against. * @return The updated JsonPatchDocument object. - * @throws NullPointerException If {@code path} or {@code rawJsonValue} is null. + * @throws NullPointerException If {@code path} is null. */ - public JsonPatchDocument appendTest(String path, String rawJsonValue) { + public JsonPatchDocument appendTest(String path, Object value) { Objects.requireNonNull(path, "'path' cannot be null."); - Objects.requireNonNull(rawJsonValue, "'rawJsonValue' cannot be null."); - operations.add(new JsonPatchOperation(JsonPatchOperationKind.TEST, path, null, rawJsonValue)); + return appendOperation(JsonPatchOperationKind.TEST, null, path, serializeValue(value)); + } + + private Optional serializeValue(Object value) { + if (value == null) { + return Optional.empty(); + } + + String rawValue; + try { + if (serializer == null) { + rawValue = MAPPER.writeValueAsString(value); + } else { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + serializer.serialize(outputStream, value); + rawValue = outputStream.toString("UTF-8"); + } + } catch (IOException ex) { + throw logger.logExceptionAsError(new UncheckedIOException(ex)); + } + + return Optional.of(rawValue); + } + + private JsonPatchDocument appendOperation(JsonPatchOperationKind operationKind, String from, String path, + Optional optionalValue) { + operations.add(new JsonPatchOperation(operationKind, from, path, optionalValue)); return this; } @@ -175,42 +217,16 @@ public JsonPatchDocument appendTest(String path, String rawJsonValue) { */ @Override public String toString() { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - try { - JsonGenerator generator = MAPPER.createGenerator(outputStream); - generator.writeStartArray(); + StringBuilder builder = new StringBuilder("["); - for (JsonPatchOperation operation : operations) { - writeOperation(generator, operation); + for (int i = 0; i < operations.size(); i++) { + if (i > 0) { + builder.append(","); } - generator.writeEndArray(); - generator.flush(); - generator.close(); - - return outputStream.toString("UTF-8"); - } catch (IOException e) { - throw logger.logExceptionAsError(new UncheckedIOException(e)); - } - } - - private static void writeOperation(JsonGenerator generator, JsonPatchOperation operation) throws IOException { - generator.writeStartObject(); - - generator.writeStringField("op", operation.getKind().toString()); - - if (operation.getFrom() != null) { - generator.writeStringField("from", operation.getFrom()); - } - - generator.writeStringField("path", operation.getPath()); - - if (operation.getRawJsonValue() != null) { - generator.writeFieldName("value"); - generator.writeTree(MAPPER.readTree(operation.getRawJsonValue())); + operations.get(i).buildString(builder); } - generator.writeEndObject(); + return builder.append("]").toString(); } } diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentSerializer.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentSerializer.java new file mode 100644 index 000000000000..26bb7ca3db48 --- /dev/null +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentSerializer.java @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.experimental.jsonpatch; + +import com.azure.core.util.CoreUtils; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import java.io.IOException; + +/** + * Handles serialization of a {@link JsonPatchDocument}. + */ +public class JsonPatchDocumentSerializer extends JsonSerializer { + private static final Module MODULE; + + static { + MODULE = new SimpleModule().addSerializer(JsonPatchDocument.class, new JsonPatchDocumentSerializer()); + } + + /** + * Gets the module for this serializer that can be added into an {@link ObjectMapper}. + * + * @return The module for this serializer. + */ + public static Module getModule() { + return MODULE; + } + + @Override + public void serialize(JsonPatchDocument value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (CoreUtils.isNullOrEmpty(value.getJsonPatchOperations())) { + return; + } + + gen.writeStartArray(value.getJsonPatchOperations().size()); + + for (JsonPatchOperation operation : value.getJsonPatchOperations()) { + gen.writeObject(operation); + } + + gen.writeEndArray(); + } +} diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperation.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperation.java index ebac4b3c5385..900a00829eb1 100644 --- a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperation.java +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperation.java @@ -3,58 +3,124 @@ package com.azure.core.experimental.jsonpatch; +import java.util.Objects; +import java.util.Optional; + /** * Represents a JSON Patch operation. */ -class JsonPatchOperation { - private final JsonPatchOperationKind kind; - private final String path; +public final class JsonPatchOperation { + private final JsonPatchOperationKind operationKind; private final String from; - private final String rawJsonValue; + private final String path; + private final Optional optionalValue; - /* - * Constructs a JSON Patch operation. + /** + * Creates a JSON Patch operation. + *

+ * When {@code optionalValue} is null the value won't be included in the JSON request, use {@link Optional#empty()} + * to indicate a JSON null. + * + * @param operationKind The kind of operation. + * @param from Optional from target path. + * @param path Operation target path. + * @param optionalValue Optional value. */ - JsonPatchOperation(JsonPatchOperationKind kind, String path, String from, String rawJsonValue) { - this.kind = kind; - this.path = path; + public JsonPatchOperation(JsonPatchOperationKind operationKind, String from, String path, + Optional optionalValue) { + this.operationKind = operationKind; this.from = from; - this.rawJsonValue = rawJsonValue; + this.path = path; + this.optionalValue = optionalValue; } /** - * Gets the JSON Patch operation kind. + * Gets the operation kind. * - * @return JSON Patch operation kind. + * @return The kind of operation. */ - public JsonPatchOperationKind getKind() { - return kind; + public JsonPatchOperationKind getOperationKind() { + return operationKind; } /** - * Gets the path that the JSON Patch operation targets. + * Gets the operation from target path. * - * @return Path the JSON Patch operation targets. + * @return The operation from target path. */ - public String getPath() { - return path; + public String getFrom() { + return from; } /** - * Gets the optional path that the JSON Patch operation uses as its source. + * Gets the operation target path. * - * @return Optional path the JSON Patch operation uses as its source. + * @return The operation target path. */ - public String getFrom() { - return from; + public String getPath() { + return path; } /** - * Gets the optional JSON value for the JSON Patch operation. + * Gets the operation value. * - * @return Optional JSON value for the JSON Patch operation. + * @return The operation value. */ - public String getRawJsonValue() { - return rawJsonValue; + public Optional getOptionalValue() { + return optionalValue; + } + + @Override + public int hashCode() { + return Objects.hash(operationKind.toString(), from, path, (optionalValue == null) ? null : optionalValue.get()); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof JsonPatchOperation)) { + return false; + } + + if (this == obj) { + return true; + } + + JsonPatchOperation other = (JsonPatchOperation) obj; + return Objects.equals(operationKind, other.operationKind) + && Objects.equals(from, other.from) + && Objects.equals(path, other.path) + && Objects.equals(optionalValue, other.optionalValue); + } + + @Override + public String toString() { + return buildString(new StringBuilder()).toString(); + } + + StringBuilder buildString(StringBuilder builder) { + builder.append("{\"op\":\"") + .append(operationKind.toString()) + .append("\""); + + if (from != null) { + builder.append(",\"from\":\"") + .append(from) + .append("\""); + } + + builder.append(",\"path\":\"") + .append(path) + .append("\""); + + if (optionalValue != null) { + builder.append(",\"value\":"); + if (optionalValue.isPresent()) { + builder.append(optionalValue.get()); + } else { + builder.append("null"); + } + } + + return builder.append("}"); } } diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationKind.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationKind.java index bdbd154ff053..5665ab15f827 100644 --- a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationKind.java +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationKind.java @@ -4,14 +4,37 @@ package com.azure.core.experimental.jsonpatch; /** - * Represents a JSON Patch operation kind. + * Represents the JSON Patch operation kind. */ -enum JsonPatchOperationKind { +public enum JsonPatchOperationKind { + /** + * Add operation. + */ ADD("add"), + + /** + * Remove operation. + */ REMOVE("remove"), + + /** + * Replace operation. + */ REPLACE("replace"), + + /** + * Move operation. + */ MOVE("move"), + + /** + * Copy operation. + */ COPY("copy"), + + /** + * Test operation. + */ TEST("test"); private final String operation; diff --git a/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationSerializer.java b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationSerializer.java new file mode 100644 index 000000000000..28567b9ebd56 --- /dev/null +++ b/sdk/core/azure-core-experimental/src/main/java/com/azure/core/experimental/jsonpatch/JsonPatchOperationSerializer.java @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.experimental.jsonpatch; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import java.io.IOException; +import java.util.Optional; + +/** + * Handles serialization of a {@link JsonPatchOperation}. + */ +public class JsonPatchOperationSerializer extends JsonSerializer { + private static final Module MODULE; + + static { + MODULE = new SimpleModule() + .addSerializer(JsonPatchOperation.class, new JsonPatchOperationSerializer()); + } + + /** + * Gets the module for this serializer that can be added into an {@link ObjectMapper}. + * + * @return The module for this serializer. + */ + public static Module getModule() { + return MODULE; + } + + @Override + public void serialize(JsonPatchOperation value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeStartObject(); + + gen.writeStringField("op", value.getOperationKind().toString()); + + String from = value.getFrom(); + if (from != null) { + gen.writeStringField("from", from); + } + + gen.writeStringField("path", value.getPath()); + + Optional optionalValue = value.getOptionalValue(); + if (optionalValue != null) { + if (optionalValue.isPresent()) { + gen.writeFieldName("value"); + gen.writeRawValue(optionalValue.get()); + } else { + gen.writeNullField("value"); + } + } + + gen.writeEndObject(); + } +} diff --git a/sdk/core/azure-core-experimental/src/main/java/module-info.java b/sdk/core/azure-core-experimental/src/main/java/module-info.java index e44c8d8d8fdf..9a7ddea4ef50 100644 --- a/sdk/core/azure-core-experimental/src/main/java/module-info.java +++ b/sdk/core/azure-core-experimental/src/main/java/module-info.java @@ -8,5 +8,7 @@ exports com.azure.core.experimental.serializer; exports com.azure.core.experimental.spatial; + opens com.azure.core.experimental.jsonpatch to com.fasterxml.jackson.databind; + uses com.azure.core.experimental.serializer.AvroSerializerProvider; } diff --git a/sdk/core/azure-core-experimental/src/samples/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentJavaDocCodeSnippet.java b/sdk/core/azure-core-experimental/src/samples/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentJavaDocCodeSnippet.java index 8ef0f6e6c2b2..c726bfcbe444 100644 --- a/sdk/core/azure-core-experimental/src/samples/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentJavaDocCodeSnippet.java +++ b/sdk/core/azure-core-experimental/src/samples/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentJavaDocCodeSnippet.java @@ -3,66 +3,68 @@ package com.azure.core.experimental.jsonpatch; +import java.util.Collections; + /** * Codesnippets for {@link JsonPatchDocument}. */ public class JsonPatchDocumentJavaDocCodeSnippet { /** - * Codesnippets for {@link JsonPatchDocument#appendAdd(String, String)}. + * Codesnippets for {@link JsonPatchDocument#appendAdd(String, Object)}. */ public void appendAdd() { JsonPatchDocument jsonPatchDocument = new JsonPatchDocument(); - // BEGIN: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendAdd#String-String + // BEGIN: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendAdd#String-Object /* * Add an object member to the JSON document { "foo" : "bar" } to get the JSON document * { "bar": "foo", "foo": "bar" }. */ - jsonPatchDocument.appendAdd("/bar", "\"foo\""); + jsonPatchDocument.appendAdd("/bar", "foo"); /* * Add an array element to the JSON document { "foo": [ "fizz", "fizzbuzz" ] } to get the JSON document * { "foo": [ "fizz", "buzz", "fizzbuzz" ] }. */ - jsonPatchDocument.appendAdd("/foo/1", "\"buzz\""); + jsonPatchDocument.appendAdd("/foo/1", "buzz"); /* * Add a nested member to the JSON document { "foo": "bar" } to get the JSON document * { "foo": "bar", "child": { "grandchild": { } } }. */ - jsonPatchDocument.appendAdd("/child", "{ \"grandchild\": { } }"); + jsonPatchDocument.appendAdd("/child", Collections.singletonMap("grandchild", Collections.emptyMap())); /* * Add an array element to the JSON document { "foo": [ "fizz", "buzz" ] } to get the JSON document * { "foo": [ "fizz", "buzz", "fizzbuzz" ] }. */ - jsonPatchDocument.appendAdd("/foo/-", "\"fizzbuzz\""); - // END: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendAdd#String-String + jsonPatchDocument.appendAdd("/foo/-", "fizzbuzz"); + // END: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendAdd#String-Object } /** - * Codesnippets for {@link JsonPatchDocument#appendReplace(String, String)}. + * Codesnippets for {@link JsonPatchDocument#appendReplace(String, Object)}. */ public void appendReplace() { JsonPatchDocument jsonPatchDocument = new JsonPatchDocument(); - // BEGIN: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendReplace#String-String + // BEGIN: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendReplace#String-Object /* * Replace an object member in the JSON document { "bar": "qux", "foo": "bar" } to get the JSON document * { "bar": "foo", "foo": "bar" }. */ - jsonPatchDocument.appendReplace("/bar", "\"foo\""); + jsonPatchDocument.appendReplace("/bar", "foo"); /* * Replace an object member in the JSON document { "foo": "fizz" } to get the JSON document * { "foo": [ "fizz", "buzz", "fizzbuzz" ] }. */ - jsonPatchDocument.appendReplace("/foo", "[ \"fizz\", \"buzz\", \"fizzbuzz\" ]"); + jsonPatchDocument.appendReplace("/foo", new String[] {"fizz", "buzz", "fizzbuzz"}); /* * Given the JSON document { "foo": "bar" } the following is an example of an invalid replace operation as the * target path doesn't exist in the document. */ - jsonPatchDocument.appendReplace("/baz", "\"foo\""); - // END: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendReplace#String-String + jsonPatchDocument.appendReplace("/baz", "foo"); + // END: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendReplace#String-Object } /** @@ -150,26 +152,26 @@ public void appendRemove() { } /** - * Codesnippets for {@link JsonPatchDocument#appendTest(String, String)}. + * Codesnippets for {@link JsonPatchDocument#appendTest(String, Object)}. */ public void appendTest() { JsonPatchDocument jsonPatchDocument = new JsonPatchDocument(); - // BEGIN: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendTest#String-String + // BEGIN: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendTest#String-Object /* * Test an object member in the JSON document { "foo": "bar" } to get a successful operation. */ - jsonPatchDocument.appendTest("/foo", "\"bar\""); + jsonPatchDocument.appendTest("/foo", "bar"); /* * Test an object member in the JSON document { "foo": "bar" } to get a unsuccessful operation. */ - jsonPatchDocument.appendTest("/foo", "42"); + jsonPatchDocument.appendTest("/foo", 42); /* * Given the JSON document { "foo": "bar" } the following is an example of an unsuccessful test operation as * the target path doesn't exist in the document. */ - jsonPatchDocument.appendTest("/baz", "\"bar\""); - // END: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendTest#String-String + jsonPatchDocument.appendTest("/baz", "bar"); + // END: com.azure.core.experimental.jsonpatch.JsonPatchDocument.appendTest#String-Object } } diff --git a/sdk/core/azure-core-experimental/src/test/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentTests.java b/sdk/core/azure-core-experimental/src/test/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentTests.java index 4001ff536595..91bcab43a47f 100644 --- a/sdk/core/azure-core-experimental/src/test/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentTests.java +++ b/sdk/core/azure-core-experimental/src/test/java/com/azure/core/experimental/jsonpatch/JsonPatchDocumentTests.java @@ -3,12 +3,14 @@ package com.azure.core.experimental.jsonpatch; -import org.junit.jupiter.api.Test; +import com.azure.core.util.serializer.JacksonAdapter; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.io.UncheckedIOException; +import java.io.IOException; +import java.util.Collections; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -18,18 +20,35 @@ * Tests {@link JsonPatchDocument}. */ public class JsonPatchDocumentTests { + private static final ObjectMapper MAPPER = ((JacksonAdapter) JacksonAdapter.createDefaultSerializerAdapter()) + .serializer() + .registerModule(JsonPatchDocumentSerializer.getModule()) + .registerModule(JsonPatchOperationSerializer.getModule()); + @ParameterizedTest - @MethodSource("toStringTestSupplier") + @MethodSource("formattingSupplier") public void toStringTest(JsonPatchDocument document, String expected) { assertEquals(expected, document.toString()); } - private static Stream toStringTestSupplier() { + @ParameterizedTest + @MethodSource("formattingSupplier") + public void jsonifyDocument(JsonPatchDocument document, String expected) throws IOException { + assertEquals(expected, MAPPER.writeValueAsString(document).replace(" ", "")); + } + + @ParameterizedTest + @MethodSource("formattingSupplier") + public void jsonifyOperationList(JsonPatchDocument document, String expected) throws IOException { + assertEquals(expected, MAPPER.writeValueAsString(document.getJsonPatchOperations()).replace(" ", "")); + } + + private static Stream formattingSupplier() { JsonPatchDocument complexDocument = new JsonPatchDocument() - .appendTest("/a/b/c", "\"foo\"") + .appendTest("/a/b/c", "foo") .appendRemove("/a/b/c") - .appendAdd("/a/b/c", "[\"foo\",\"bar\"]") - .appendReplace("/a/b/c", "42") + .appendAdd("/a/b/c", new String[] {"foo", "bar"}) + .appendReplace("/a/b/c", 42) .appendMove("/a/b/c", "/a/b/d") .appendCopy("/a/b/d", "/a/b/e"); @@ -43,25 +62,26 @@ private static Stream toStringTestSupplier() { + "]"; return Stream.of( - Arguments.of(new JsonPatchDocument().appendAdd("/baz", "\"qux\""), + Arguments.of(new JsonPatchDocument().appendAdd("/baz", "qux"), constructExpectedOperation("add", null, "/baz", "qux")), - Arguments.of(new JsonPatchDocument().appendAdd("/foo/1", "\"qux\""), + Arguments.of(new JsonPatchDocument().appendAdd("/foo/1", "qux"), constructExpectedOperation("add", null, "/foo/1", "qux")), - Arguments.of(new JsonPatchDocument().appendAdd("/child", "{ \"grandchild\": { } }"), + Arguments.of(new JsonPatchDocument().appendAdd("/child", + Collections.singletonMap("grandchild", Collections.emptyMap())), constructExpectedOperation("add", null, "/child", "{\"grandchild\":{}}", false)), - Arguments.of(new JsonPatchDocument().appendAdd("/foo/-", "[\"abc\", \"def\"]"), + Arguments.of(new JsonPatchDocument().appendAdd("/foo/-", new String[] {"abc", "def"}), constructExpectedOperation("add", null, "/foo/-", "[\"abc\",\"def\"]", false)), - Arguments.of(new JsonPatchDocument().appendReplace("/bar", "\"foo\""), + Arguments.of(new JsonPatchDocument().appendReplace("/bar", "foo"), constructExpectedOperation("replace", null, "/bar", "foo")), - Arguments.of(new JsonPatchDocument().appendReplace("/foo", "[ \"fizz\", \"buzz\", \"fizzbuzz\" ]"), + Arguments.of(new JsonPatchDocument().appendReplace("/foo", new String[] {"fizz", "buzz", "fizzbuzz"}), constructExpectedOperation("replace", null, "/foo", "[\"fizz\",\"buzz\",\"fizzbuzz\"]", false)), - Arguments.of(new JsonPatchDocument().appendReplace("/baz", "\"foo\""), + Arguments.of(new JsonPatchDocument().appendReplace("/baz", "foo"), constructExpectedOperation("replace", null, "/baz", "foo")), Arguments.of(new JsonPatchDocument().appendCopy("/foo", "/copy"), @@ -94,13 +114,13 @@ private static Stream toStringTestSupplier() { Arguments.of(new JsonPatchDocument().appendRemove("/baz"), constructExpectedOperation("remove", null, "/baz", null)), - Arguments.of(new JsonPatchDocument().appendTest("/foo", "\"bar\""), + Arguments.of(new JsonPatchDocument().appendTest("/foo", "bar"), constructExpectedOperation("test", null, "/foo", "bar")), - Arguments.of(new JsonPatchDocument().appendTest("/foo", "42"), + Arguments.of(new JsonPatchDocument().appendTest("/foo", 42), constructExpectedOperation("test", null, "/foo", "42", false)), - Arguments.of(new JsonPatchDocument().appendTest("/baz", "\"bar\""), + Arguments.of(new JsonPatchDocument().appendTest("/baz", "bar"), constructExpectedOperation("test", null, "/baz", "bar")), Arguments.of(complexDocument, complexExpected) @@ -141,10 +161,8 @@ private static Stream invalidArgumentSupplier() { JsonPatchDocument document = new JsonPatchDocument(); return Stream.of( Arguments.of((Runnable) () -> document.appendAdd(null, "\"bar\"")), - Arguments.of((Runnable) () -> document.appendAdd("/foo", null)), Arguments.of((Runnable) () -> document.appendReplace(null, "\"bar\"")), - Arguments.of((Runnable) () -> document.appendReplace("/foo", null)), Arguments.of((Runnable) () -> document.appendCopy(null, "\"bar\"")), Arguments.of((Runnable) () -> document.appendCopy("/foo", null)), @@ -154,15 +172,7 @@ private static Stream invalidArgumentSupplier() { Arguments.of((Runnable) () -> document.appendRemove(null)), - Arguments.of((Runnable) () -> document.appendTest(null, "\"bar\"")), - Arguments.of((Runnable) () -> document.appendTest("/foo", null)) + Arguments.of((Runnable) () -> document.appendTest(null, "\"bar\"")) ); } - - @Test - public void invalidRawJson() { - assertThrows(UncheckedIOException.class, () -> new JsonPatchDocument() - .appendTest("/a/b/c", "foo") - .toString()); - } }