Skip to content

Commit

Permalink
Update JSON Patch Functionality (#15430)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alzimmermsft authored Sep 23, 2020
1 parent 378d920 commit 27fdfd6
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -572,4 +572,8 @@

<!-- Suppress Event Grid checks for now -->
<suppress checks="[a-zA-Z0-9]*" files="[/\\]azure-messaging-eventgrid[/\\]src[/\\]"/>

<!-- Suppress checks in azure-core-experimental -->
<suppress checks="com.azure.tools.checkstyle.checks.ExternalDependencyExposedCheck"
files="com.azure.core.experimental.jsonpatch.(JsonPatchDocumentSerializer|JsonPatchOperationSerializer).java"/>
</suppressions>
1 change: 1 addition & 0 deletions sdk/core/azure-core-experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -25,12 +27,34 @@ public class JsonPatchDocument {
private final ClientLogger logger = new ClientLogger(JsonPatchDocument.class);

private final List<JsonPatchOperation> operations;
private final JsonSerializer serializer;

/**
* Creates a new JSON Patch document.
*/
public JsonPatchDocument() {
this(null);
}

/**
* Creates a new JSON Patch document.
* <p>
* 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<JsonPatchOperation> getJsonPatchOperations() {
return Collections.unmodifiableList(operations);
}

/**
Expand All @@ -43,19 +67,17 @@ public JsonPatchDocument() {
*
* <p><strong>Code Samples</strong></p>
*
* {@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));
}

/**
Expand All @@ -65,19 +87,17 @@ public JsonPatchDocument appendAdd(String path, String rawJsonValue) {
*
* <p><strong>Code Samples</strong></p>
*
* {@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));
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -153,18 +170,43 @@ public JsonPatchDocument appendRemove(String path) {
*
* <p><strong>Code Samples</strong></p>
*
* {@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<String> 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<String> optionalValue) {
operations.add(new JsonPatchOperation(operationKind, from, path, optionalValue));
return this;
}

Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<JsonPatchDocument> {
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();
}
}
Loading

0 comments on commit 27fdfd6

Please sign in to comment.