Skip to content

Commit a862760

Browse files
committed
WIP: Add JVM-based JSON formatter
Currenly, if consumers of Spotless want to format their JSON files, they need to do this by adding `prettier`, which increases the dependencies a project needs. To simplify things for consumers, as documented in diffplug#850, we can provide a JVM-based JSON formatter, using `org.json`'s JSON formatting. To follow how we've done this with other formatters, we can retrieve `org.json`'s JAR at runtime, and retrieve the classes via Reflection, instead of adding this as a `lib-extra` project, with an explicit dependency. To validate this fully, we want a few straightforward JSON files to validate before/after, but we can also use [a sample Cucumber JSON report] as a much more complex example of what happens, which we've removed any `data` objects from its source, so the files are smaller. We also want our consumers to be able to configure the indentation size. [a sample Cucumber JSON report]: https://github.com/damianszczepanik/cucumber-reporting/raw/master/src/test/resources/json/sample.json
1 parent c09f3cb commit a862760

24 files changed

+1522
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2021 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.json;
17+
18+
import java.io.IOException;
19+
import java.io.Serializable;
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.lang.reflect.Method;
23+
import java.util.Objects;
24+
25+
import com.diffplug.spotless.FormatterFunc;
26+
import com.diffplug.spotless.FormatterStep;
27+
import com.diffplug.spotless.JarState;
28+
import com.diffplug.spotless.Provisioner;
29+
30+
public final class JsonFormatterStep {
31+
private static final String MAVEN_COORDINATE = "org.json:json:";
32+
private static final String DEFAULT_VERSION = "20210307";
33+
34+
public static FormatterStep create(Integer indent, Provisioner provisioner) {
35+
Objects.requireNonNull(provisioner, "provisioner cannot be null");
36+
return FormatterStep.createLazy("json", () -> new State(indent, provisioner), State::toFormatter);
37+
}
38+
39+
private static final class State implements Serializable {
40+
private static final long serialVersionUID = 1L;
41+
42+
private final int indentSpaces;
43+
private final boolean useTabs;
44+
private final JarState jarState;
45+
46+
private State(Integer indent, Provisioner provisioner) throws IOException {
47+
if (indent != null) {
48+
this.indentSpaces = indent;
49+
this.useTabs = false;
50+
} else {
51+
this.indentSpaces = 20; // it's unlikely that someone will use this many spaces, so should be safe
52+
useTabs = true;
53+
}
54+
55+
this.jarState = JarState.from(MAVEN_COORDINATE + DEFAULT_VERSION, provisioner);
56+
}
57+
58+
FormatterFunc toFormatter() {
59+
Method objectToString;
60+
Method arrayToString;
61+
Constructor<?> objectConstructor;
62+
Constructor<?> arrayConstructor;
63+
try {
64+
ClassLoader classLoader = jarState.getClassLoader();
65+
Class<?> jsonObject = classLoader.loadClass("org.json.JSONObject");
66+
Class<?>[] constructorArguments = new Class[]{String.class};
67+
objectConstructor = jsonObject.getConstructor(constructorArguments);
68+
objectToString = jsonObject.getMethod("toString", int.class);
69+
70+
Class<?> jsonArray = classLoader.loadClass("org.json.JSONArray");
71+
arrayConstructor = jsonArray.getConstructor(constructorArguments);
72+
arrayToString = jsonArray.getMethod("toString", int.class);
73+
} catch (ClassNotFoundException | NoSuchMethodException e) {
74+
throw new IllegalStateException("There was a problem preparing org.json dependencies", e);
75+
}
76+
77+
return s -> {
78+
String prettyPrinted = null;
79+
if (s.isEmpty()) {
80+
prettyPrinted = s;
81+
}
82+
if (s.startsWith("{")) {
83+
try {
84+
Object parsed = objectConstructor.newInstance(s);
85+
prettyPrinted = objectToString.invoke(parsed, indentSpaces) + "\n";
86+
} catch (InvocationTargetException ignored) {
87+
// ignore if we cannot convert to JSON string
88+
}
89+
}
90+
if (s.startsWith("[")) {
91+
try {
92+
Object parsed = arrayConstructor.newInstance(s);
93+
prettyPrinted = arrayToString.invoke(parsed, indentSpaces) + "\n";
94+
} catch (InvocationTargetException ignored) {
95+
// ignore if we cannot convert to JSON string
96+
}
97+
}
98+
99+
if (prettyPrinted == null) {
100+
throw new AssertionError("Invalid JSON file provided");
101+
}
102+
103+
return prettyPrinted;
104+
};
105+
}
106+
}
107+
108+
private JsonFormatterStep() {
109+
// cannot be directly instantiated
110+
}
111+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@ParametersAreNonnullByDefault
2+
@ReturnValuesAreNonnullByDefault
3+
package com.diffplug.spotless.extra.json.java;
4+
5+
import javax.annotation.ParametersAreNonnullByDefault;
6+
7+
import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault;

0 commit comments

Comments
 (0)