Skip to content

Commit

Permalink
Add JsonPatchStep (#1753)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Jul 31, 2023
2 parents b6e30f8 + 1eb2a4f commit 19b7c2d
Show file tree
Hide file tree
Showing 18 changed files with 400 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,6 @@ nb-configuration.xml

# MacOS jenv
.java-version

# VS Code
.vscode/
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
### Added
* Add a `jsonPatch` step to `json` formatter configurations. This allows patching of JSON documents using [JSON Patches](https://jsonpatch.com). ([#1753](https://github.com/diffplug/spotless/pull/1753))
### Fixed
* Use latest versions of popular style guides for `eslint` tests to fix failing `useEslintXoStandardRules` test. ([#1761](https://github.com/diffplug/spotless/pull/1761), [#1756](https://github.com/diffplug/spotless/issues/1756))
* Add support for `prettier` version `3.0.0` and newer. ([#1760]https://github.com/diffplug/spotless/pull/1760), [#1751](https://github.com/diffplug/spotless/issues/1751))
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ lib('java.CleanthatJavaStep') +'{{yes}} | {{yes}}
lib('json.gson.GsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('json.JacksonJsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('json.JsonSimpleStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('json.JsonPatchStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('kotlin.KtLintStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('kotlin.DiktatStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
Expand Down Expand Up @@ -140,6 +141,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}}
| [`json.gson.GsonStep`](lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`json.JacksonJsonStep`](lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`json.JsonSimpleStep`](lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`json.JsonPatchStep`](lib/src/main/java/com/diffplug/spotless/json/JsonPatchStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`kotlin.KtLintStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
| [`kotlin.KtfmtStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`kotlin.DiktatStep`](lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
Expand Down
5 changes: 4 additions & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def NEEDS_GLUE = [
'ktlint',
'palantirJavaFormat',
'scalafmt',
'sortPom'
'sortPom',
'zjsonPatch',
]
for (glue in NEEDS_GLUE) {
sourceSets.register(glue) {
Expand Down Expand Up @@ -114,6 +115,8 @@ dependencies {
// sortPom
sortPomCompileOnly 'com.github.ekryd.sortpom:sortpom-sorter:3.2.1'
sortPomCompileOnly 'org.slf4j:slf4j-api:2.0.0'
// zjsonPatch
zjsonPatchCompileOnly 'com.flipkart.zjsonpatch:zjsonpatch:0.4.14'
}

// we'll hold the core lib to a high standard
Expand Down
90 changes: 90 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/json/JsonPatchStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2023 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.spotless.json;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;

public class JsonPatchStep {
// https://mvnrepository.com/artifact/com.flipkart.zjsonpatch/zjsonpatch
static final String MAVEN_COORDINATE = "com.flipkart.zjsonpatch:zjsonpatch";
static final String DEFAULT_VERSION = "0.4.14";

private JsonPatchStep() {}

public static FormatterStep create(String patchString, Provisioner provisioner) {
return create(DEFAULT_VERSION, patchString, provisioner);
}

public static FormatterStep create(String zjsonPatchVersion, String patchString, Provisioner provisioner) {
Objects.requireNonNull(zjsonPatchVersion, "zjsonPatchVersion cannot be null");
Objects.requireNonNull(patchString, "patchString cannot be null");
Objects.requireNonNull(provisioner, "provisioner cannot be null");
return FormatterStep.createLazy("apply-json-patch", () -> new State(zjsonPatchVersion, patchString, provisioner), State::toFormatter);
}

public static FormatterStep create(List<Map<String, Object>> patch, Provisioner provisioner) {
return create(DEFAULT_VERSION, patch, provisioner);
}

public static FormatterStep create(String zjsonPatchVersion, List<Map<String, Object>> patch, Provisioner provisioner) {
Objects.requireNonNull(zjsonPatchVersion, "zjsonPatchVersion cannot be null");
Objects.requireNonNull(patch, "patch cannot be null");
Objects.requireNonNull(provisioner, "provisioner cannot be null");
return FormatterStep.createLazy("apply-json-patch", () -> new State(zjsonPatchVersion, patch, provisioner), State::toFormatter);
}

static final class State implements Serializable {
private static final long serialVersionUID = 1L;

private final JarState jarState;
private final List<Map<String, Object>> patch;
private final String patchString;

private State(String zjsonPatchVersion, List<Map<String, Object>> patch, Provisioner provisioner) throws IOException {
this.jarState = JarState.from(MAVEN_COORDINATE + ":" + zjsonPatchVersion, provisioner);
this.patch = patch;
this.patchString = null;
}

private State(String zjsonPatchVersion, String patchString, Provisioner provisioner) throws IOException {
this.jarState = JarState.from(MAVEN_COORDINATE + ":" + zjsonPatchVersion, provisioner);
this.patch = null;
this.patchString = patchString;
}

FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.json.JsonPatchFormatterFunc");
if (this.patch != null) {
Constructor<?> constructor = formatterFunc.getConstructor(List.class);
return (FormatterFunc) constructor.newInstance(patch);
} else {
Constructor<?> constructor = formatterFunc.getConstructor(String.class);
return (FormatterFunc) constructor.newInstance(patchString);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2023 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.spotless.glue.json;

import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.zjsonpatch.JsonPatch;

import com.diffplug.spotless.FormatterFunc;

public class JsonPatchFormatterFunc implements FormatterFunc {
private final ObjectMapper objectMapper;
private final List<Map<String, Object>> patch;
private final String patchString;

public JsonPatchFormatterFunc(String patchString) {
this.objectMapper = new ObjectMapper();
this.patch = null;
this.patchString = patchString;
}

public JsonPatchFormatterFunc(List<Map<String, Object>> patch) {
this.objectMapper = new ObjectMapper();
this.patch = patch;
this.patchString = null;
}

@Override
public String apply(String input) throws Exception {
var patchNode = this.patch == null
? objectMapper.readTree(patchString)
: objectMapper.valueToTree(patch);

var inputNode = objectMapper.readTree(input);

var patchedNode = JsonPatch.apply(patchNode, inputNode);

return objectMapper.writeValueAsString(patchedNode);
}
}
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).

## [Unreleased]
### Added
* Add a `jsonPatch` step to `json` formatter configurations. This allows patching of JSON documents using [JSON Patches](https://jsonpatch.com). ([#1753](https://github.com/diffplug/spotless/pull/1753))
### Fixed
* Add support for `prettier` version `3.0.0` and newer. ([#1760]https://github.com/diffplug/spotless/pull/1760), [#1751](https://github.com/diffplug/spotless/issues/1751))
* Fix npm install calls when npm cache is not up-to-date. ([#1760]https://github.com/diffplug/spotless/pull/1760), [#1750](https://github.com/diffplug/spotless/issues/1750))
Expand Down
44 changes: 44 additions & 0 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,7 @@ spotless {
gson() // has its own section below
jackson() // has its own section below
rome() // has its own section below
jsonPatch([]) // has its own section below
}
}
```
Expand Down Expand Up @@ -872,6 +873,49 @@ spotless {
}
```
### jsonPatch
Uses [zjsonpatch](https://github.com/flipkart-incubator/zjsonpatch) to apply [JSON Patches](https://jsonpatch.com/) as per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902/) to JSON documents.
This enables you to add, replace or remove properties at locations in the JSON document that you specify using [JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901/).
In Spotless Gradle, these JSON patches are represented as a `List<Map<String, Object>>`, or a list of patch operations.
Each patch operation must be a map with the following properties:
* `"op"` - the operation to apply, one of `"replace"`, `"add"` or `"remove"`.
* `"path"` - a JSON Pointer string, for example `"/foo"`
* `"value"` - the value to `"add"` or `"replace"` at the specified path. Not needed for `"remove"` operations.
For example, to apply the patch from the [JSON Patch homepage](https://jsonpatch.com/#the-patch):
```gradle
spotless {
json {
target 'src/**/*.json'
jsonPatch([
[op: 'replace', path: '/baz', value: 'boo'],
[op: 'add', path: '/hello', value: ['world']],
[op: 'remove', path: '/foo']
])
}
}
```
Or using the Kotlin DSL:
```kotlin
spotless {
json {
target("src/**/*.json")
jsonPatch(listOf(
mapOf("op" to "replace", "path" to "/baz", "value" to "boo"),
mapOf("op" to "add", "path" to "/hello", "value" to listOf("world")),
mapOf("op" to "remove", "path" to "/foo")
))
}
}
```
## YAML
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,22 @@
package com.diffplug.gradle.spotless;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.json.JacksonJsonConfig;
import com.diffplug.spotless.json.JacksonJsonStep;
import com.diffplug.spotless.json.JsonPatchStep;
import com.diffplug.spotless.json.JsonSimpleStep;
import com.diffplug.spotless.json.gson.GsonStep;

public class JsonExtension extends FormatExtension {
private static final int DEFAULT_INDENTATION = 4;
private static final String DEFAULT_GSON_VERSION = "2.10.1";
private static final String DEFAULT_ZJSONPATCH_VERSION = "0.4.14";
static final String NAME = "json";

@Inject
Expand Down Expand Up @@ -71,6 +75,14 @@ public RomeJson rome(String version) {
return romeConfig;
}

public JsonPatchConfig jsonPatch(List<Map<String, Object>> patch) {
return new JsonPatchConfig(patch);
}

public JsonPatchConfig jsonPatch(String zjsonPatchVersion, List<Map<String, Object>> patch) {
return new JsonPatchConfig(zjsonPatchVersion, patch);
}

public class SimpleConfig {
private int indent;

Expand Down Expand Up @@ -191,4 +203,29 @@ protected RomeJson getThis() {
return this;
}
}

public class JsonPatchConfig {
private String zjsonPatchVersion;
private List<Map<String, Object>> patch;

public JsonPatchConfig(List<Map<String, Object>> patch) {
this(DEFAULT_ZJSONPATCH_VERSION, patch);
}

public JsonPatchConfig(String zjsonPatchVersion, List<Map<String, Object>> patch) {
this.zjsonPatchVersion = zjsonPatchVersion;
this.patch = patch;
addStep(createStep());
}

public JsonPatchConfig version(String zjsonPatchVersion) {
this.zjsonPatchVersion = zjsonPatchVersion;
replaceStep(createStep());
return this;
}

private FormatterStep createStep() {
return JsonPatchStep.create(zjsonPatchVersion, patch, provisioner());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,44 @@ void jacksonFormattingWithSortingByKeys() throws IOException {
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/resources/example.json").sameAsResource("json/sortByKeysAfter_Jackson.json");
}

@Test
void jsonPatchReplaceString() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'java'",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless {",
" json {",
" target 'src/**/*.json'",
" jsonPatch([[op: 'replace', path: '/abc', value: 'ghi']])",
" gson()",
" }",
"}");
setFile("src/main/resources/example.json").toResource("json/patchObjectBefore.json");
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/resources/example.json").sameAsResource("json/patchObjectAfterReplaceString.json");
}

@Test
void jsonPatchReplaceWithObject() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'java'",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless {",
" json {",
" target 'src/**/*.json'",
" jsonPatch([[op: 'replace', path: '/abc', value: [def: 'ghi']]])",
" gson()",
" }",
"}");
setFile("src/main/resources/example.json").toResource("json/patchObjectBefore.json");
gradleRunner().withArguments("spotlessApply").build();
assertFile("src/main/resources/example.json").sameAsResource("json/patchObjectAfterReplaceWithObject.json");
}
}
2 changes: 2 additions & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
### Added
* Add a `jsonPatch` step to `json` formatter configurations. This allows patching of JSON documents using [JSON Patches](https://jsonpatch.com). ([#1753](https://github.com/diffplug/spotless/pull/1753))
### Fixed
* Add support for `prettier` version `3.0.0` and newer. ([#1760]https://github.com/diffplug/spotless/pull/1760), [#1751](https://github.com/diffplug/spotless/issues/1751))
* Fix npm install calls when npm cache is not up-to-date. ([#1760]https://github.com/diffplug/spotless/pull/1760), [#1750](https://github.com/diffplug/spotless/issues/1750))
Expand Down
Loading

0 comments on commit 19b7c2d

Please sign in to comment.