diff --git a/src/main/java/me/itzg/helpers/patch/Json5FileFormat.java b/src/main/java/me/itzg/helpers/patch/Json5FileFormat.java new file mode 100644 index 00000000..81bbbe12 --- /dev/null +++ b/src/main/java/me/itzg/helpers/patch/Json5FileFormat.java @@ -0,0 +1,64 @@ +package me.itzg.helpers.patch; + +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import java.io.IOException; +import java.util.Map; + +public class Json5FileFormat implements FileFormat { + + private static final String[] SUFFIXES = {"json5"}; + private static final TypeReference> MAP_TYPE + = new TypeReference>() { + }; + + private final ObjectMapper objectMapper; + private final ObjectWriter objectWriter; + + // Currently, missing additional whitespace character support and hexadecimal numbers + // Check https://github.com/FasterXML/jackson-core/wiki/JsonReadFeatures to see if/when they become available + public Json5FileFormat() { + objectMapper = JsonMapper.builder().enable( + JsonReadFeature.ALLOW_TRAILING_COMMA, + JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS, + JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS, + JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS, + JsonReadFeature.ALLOW_JAVA_COMMENTS, + JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, + JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS, + JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES, + JsonReadFeature.ALLOW_SINGLE_QUOTES, + JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS + ).build(); + + objectWriter = objectMapper.writer( + new DefaultPrettyPrinter() + .withoutSpacesInObjectEntries() + ); + } + + @Override + public String[] getFileSuffixes() { + return SUFFIXES; + } + + @Override + public String getName() { + return "json5"; + } + + @Override + public Map decode(String content) throws IOException { + return objectMapper.readValue(content, MAP_TYPE); + } + + @Override + public String encode(Map content) throws IOException { + return objectWriter.writeValueAsString(content); + } +} diff --git a/src/main/java/me/itzg/helpers/patch/PatchSetProcessor.java b/src/main/java/me/itzg/helpers/patch/PatchSetProcessor.java index 7749078f..61db134b 100644 --- a/src/main/java/me/itzg/helpers/patch/PatchSetProcessor.java +++ b/src/main/java/me/itzg/helpers/patch/PatchSetProcessor.java @@ -29,6 +29,7 @@ public PatchSetProcessor(Interpolator interpolator) { private final FileFormat[] fileFormats = new FileFormat[]{ new JsonFileFormat(), + new Json5FileFormat(), new YamlFileFormat() }; diff --git a/src/test/java/me/itzg/helpers/patch/PatchSetProcessorTest.java b/src/test/java/me/itzg/helpers/patch/PatchSetProcessorTest.java index 4381888b..eb1ee614 100644 --- a/src/test/java/me/itzg/helpers/patch/PatchSetProcessorTest.java +++ b/src/test/java/me/itzg/helpers/patch/PatchSetProcessorTest.java @@ -54,6 +54,32 @@ void setInJson(@TempDir Path tempDir) throws IOException { ); } + @Test + void setInJson5(@TempDir Path tempDir) throws IOException { + final Path src = tempDir.resolve("testing.json5"); + Files.copy(Paths.get("src/test/resources/patch/testing.json5"), src); + + final PatchSetProcessor processor = new PatchSetProcessor( + new Interpolator(environmentVariablesProvider, "CFG_") + ); + + processor.process(new PatchSet() + .setPatches(Arrays.asList( + new PatchDefinition() + .setFile(src) + .setOps(Arrays.asList( + new PatchSetOperation() + .setPath("$.outer.field1") + .setValue(new TextNode("new value")) + )) + )) + ); + + assertThat(src).hasSameTextualContentAs( + Paths.get("src/test/resources/patch/expected-setInJson5.json5") + ); + } + @Test void setInYaml(@TempDir Path tempDir) throws IOException { final Path src = tempDir.resolve("testing.yaml"); diff --git a/src/test/resources/patch/expected-setInJson5.json5 b/src/test/resources/patch/expected-setInJson5.json5 new file mode 100644 index 00000000..56003edd --- /dev/null +++ b/src/test/resources/patch/expected-setInJson5.json5 @@ -0,0 +1,14 @@ +{ + "outer":{ + "field1":"new value" + }, + "unquoted":"and you can quote me on that", + "singleQuotes":"I can use \"double quotes\" here", + "lineBreaks":"Look, Mom! \nNo \\n's!", + "leadingDecimalPoint":0.8675309, + "andTrailing":8675309.0, + "positiveSign":1, + "trailingComma":"in objects", + "andIn":[ "arrays" ], + "backwardsCompatible":"with JSON" +} \ No newline at end of file diff --git a/src/test/resources/patch/testing.json5 b/src/test/resources/patch/testing.json5 new file mode 100644 index 00000000..1c09f56d --- /dev/null +++ b/src/test/resources/patch/testing.json5 @@ -0,0 +1,14 @@ +{ + // comments + outer: { + field1: "old" + }, + unquoted: 'and you can quote me on that', + singleQuotes: 'I can use "double quotes" here', + lineBreaks: "Look, Mom! \ +No \\n's!", + leadingDecimalPoint: .8675309, andTrailing: 8675309., + positiveSign: +1, + trailingComma: 'in objects', andIn: ['arrays',], + "backwardsCompatible": "with JSON", +} \ No newline at end of file