Skip to content

An impl for read/write Json5 comments- patch3 #27

@Zim-Inn

Description

@Zim-Inn
Index: src/test/java/de/marhali/json5/TestJson5Comments.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/de/marhali/json5/TestJson5Comments.java b/src/test/java/de/marhali/json5/TestJson5Comments.java
new file mode 100644
--- /dev/null	(date 1757065878332)
+++ b/src/test/java/de/marhali/json5/TestJson5Comments.java	(date 1757065878332)
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 Ultreon Team
+ *
+ * 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 de.marhali.json5;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+/**
+ * Unit tests for comment handling in the {@link Json5} core class.
+ *
+ * @author Ultreon Team
+ */
+public class TestJson5Comments {
+    private Json5 json5;
+
+    private static Json5Object getJson5Object() {
+        Json5Object root = new Json5Object();
+        root.setComment("Root object comment");
+
+        Json5Boolean enabled = new Json5Boolean(true);
+        enabled.setComment("This is the enabled flag");
+        root.add("enabled", enabled);
+
+        Json5Array services = new Json5Array();
+
+        Json5String serviceName = new Json5String("auth-service");
+        serviceName.setComment("Authentication service");
+        services.add(serviceName);
+
+        root.add("services", services);
+        root.setComment("services", "List of services");
+        return root;
+    }
+
+    private InputStream getTestResource(String fileName) {
+        return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
+    }
+
+    private String getTestResourceContent(String fileName) throws IOException {
+        try (BufferedInputStream bis = new BufferedInputStream(getTestResource(fileName))) {
+            ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+            for (int result = bis.read(); result != -1; result = bis.read()) {
+                buf.write((byte) result);
+            }
+
+            return Optional.ofNullable(buf.toString(StandardCharsets.UTF_8)).map(s -> s.replace("\r\n", "\n")).map(
+                s -> s.replace("\r", "\n")).orElse(null);
+        }
+    }
+
+    @BeforeEach
+    void setup() {
+        json5 = Json5.builder(
+            builder -> builder.allowInvalidSurrogate().readComments().quoteSingle().indentFactor(2).build());
+    }
+
+    @Test
+    void parseAndVerifyComments() throws IOException {
+        try (InputStream stream = getTestResource("test.comments.json5")) {
+            Json5Element element = json5.parse(stream);
+            assertTrue(element.isJson5Object());
+
+            Json5Object obj = element.getAsJson5Object();
+
+            // Verify comments are read correctly
+            assertEquals("The master switch for the feature", obj.get("enabled").getComment());
+            assertEquals("A list of network ports", obj.get("ports").getComment());
+
+            Json5Array ports = obj.getAsJson5Array("ports");
+            assertEquals("Standard port", ports.get(0).getComment());
+            assertEquals("Alternate port", ports.get(1).getComment());
+            assertEquals("""
+                         /**
+                          * Admin port
+                          * Do not expose publicly
+                          */\
+                         """, ports.get(2).getComment());
+
+            Json5Object user = obj.getAsJson5Object("user");
+            assertEquals("User configuration", user.getComment());
+            assertEquals("The user's login name", user.get("name").getComment());
+        }
+    }
+
+    @Test
+    void serializeWithComments() throws IOException {
+        try (InputStream stream = getTestResource("test.comments.json5")) {
+            Json5Element element = json5.parse(stream);
+            String expected = getTestResourceContent("expect.comments.json5");
+            assertEquals(expected, json5.serialize(element));
+        }
+    }
+
+    @Test
+    void 少引号顶部注释测试() throws IOException {
+        try (InputStream stream = getTestResource("expect.quoteless.comment.json5")) {
+            Json5Options options =
+                new Json5OptionsBuilder().quoteless().quoteSingle().readComments().prettyPrinting().build();
+            Json5 json = new Json5(options);
+            Json5Element element = json.parse(stream);
+            String expected = getTestResourceContent("expect.quoteless.comment.json5");
+            assertEquals(expected, json.serialize(element));
+        }
+    }
+
+    @Test
+    void 深拷贝与无注释拷贝测试() throws IOException {
+        try (InputStream stream = getTestResource("expect.quoteless.comment.json5")) {
+            Json5Options options =
+                new Json5OptionsBuilder().quoteless().quoteSingle().readComments().prettyPrinting().build();
+            Json5 json = new Json5(options);
+            Json5Element element = json.parse(stream);
+            assertEquals(getTestResourceContent("expect.quoteless.comment.json5"), json.serialize(element.deepCopy()));
+            assertEquals(getTestResourceContent("expect.无注释拷贝.json5"), json.serialize(element.noCommentCopy()));
+        }
+    }
+
+    @Test
+    void programmaticallyAddComments() throws IOException {
+        Json5Object root = getJson5Object();
+
+        String expected = """
+                          // Root object comment
+                          {
+                            // This is the enabled flag
+                            'enabled': true,
+                            // List of services
+                            'services': [
+                              // Authentication service
+                              'auth-service'
+                            ]
+                          }""";
+        assertEquals(expected, json5.serialize(root));
+        assertEquals("""
+                     {"enabled":true,"services":["auth-service"]}\
+                     """, root.toStandardString());
+    }
+
+    @Test
+    void testCopyCommentToVariants() {
+        // 基础元素注释复制
+        Json5String sourceStr = new Json5String("hello");
+        sourceStr.setComment("这是一个字符串注释");
+
+        Json5String targetStr = new Json5String("hello");
+        sourceStr.copyCommentTo(targetStr);
+
+        assertEquals("这是一个字符串注释", targetStr.getComment(), "字符串注释应被复制");
+
+        // 数组注释复制(包括元素注释)
+        Json5Element sourceArr = json5.parse("""
+                                             // 数组注释
+                                             [// 注释A
+                                             'a']
+                                             """);
+
+        Json5Array targetArr = new Json5Array();
+        targetArr.add(new Json5String("a"));
+        targetArr.add(new Json5String("b"));
+
+        sourceArr.getAsJson5Array().mergeCommentTo(targetArr);
+
+        assertEquals("数组注释", targetArr.getComment(), "数组注释应被复制");
+        assertEquals("注释A", targetArr.get(0).getComment(), "元素0注释应被复制");
+
+        // 对象注释复制(键注释)
+        Json5Object sourceObj = json5.parse("""
+                                            // 对象注释
+                                            {
+                                            // 注释X
+                                            x:1,
+                                            // 注释Y
+                                            y:2
+                                            }
+                                            """).getAsJson5Object();
+        Json5Object targetObj = new Json5Object();
+        targetObj.add("x", new Json5Number(1));
+        targetObj.add("y", new Json5Number(2));
+
+        sourceObj.mergeCommentTo(targetObj);
+
+        assertEquals("对象注释", targetObj.getComment(), "对象注释应被复制");
+        assertEquals("注释X", targetObj.getComment("x"), "键x注释应被复制");
+        assertEquals("注释Y", targetObj.getComment("y"), "键y注释应被复制");
+    }
+}
Index: src/test/java/de/marhali/json5/TestJson5Object.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/de/marhali/json5/TestJson5Object.java b/src/test/java/de/marhali/json5/TestJson5Object.java
--- a/src/test/java/de/marhali/json5/TestJson5Object.java	(revision d220224342fac20d9636fe37b6379afe2faef53e)
+++ b/src/test/java/de/marhali/json5/TestJson5Object.java	(date 1757065878306)
@@ -16,19 +16,21 @@
 
 package de.marhali.json5;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
 import org.junit.jupiter.api.Test;
 
 import java.util.Set;
 
-import static org.junit.jupiter.api.Assertions.*;
-
 /**
  * Unit tests for {@link Json5Object}
  *
  * @author Marcel Haßlinger
  */
 public class TestJson5Object {
-
     @Test
     void remove() {
         Json5Object element = new Json5Object();
Index: src/test/java/de/marhali/json5/TestJson5Options.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/de/marhali/json5/TestJson5Options.java b/src/test/java/de/marhali/json5/TestJson5Options.java
--- a/src/test/java/de/marhali/json5/TestJson5Options.java	(revision d220224342fac20d9636fe37b6379afe2faef53e)
+++ b/src/test/java/de/marhali/json5/TestJson5Options.java	(date 1754548047415)
@@ -36,7 +36,9 @@
     void singleQuoted() {
         String payload = "['hello',1,'two']";
 
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
 
@@ -47,7 +49,9 @@
     void doubleQuoted() {
         String payload = "['hello',1,'two']";
 
-        Json5Options options = new Json5Options(true, false, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, false, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
 
@@ -58,7 +62,9 @@
     void trailingComma() {
         String payload = "['hello',1,'two']";
 
-        Json5Options options = new Json5Options(true, true, true, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, true, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
 
@@ -69,10 +75,39 @@
     void prettyPrinting() {
         String payload = "['hello',1,'two']";
 
-        Json5Options options = new Json5Options(true, true, true, 2);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, true, 2, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
 
         assertEquals("[\n  'hello',\n  1,\n  'two',\n]", element.toString(options));
     }
+
+    //<editor-fold desc="Modified by Ultreon (added test for quoteless)">
+    @Test
+    void quoteless() {
+        String payload = "{hello: 'world', key: 'value'}";
+
+        Json5Options options = new Json5Options(true, true, true, 2, true);
+        Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
+        Json5Object element = Json5Parser.parseObject(lexer);
+
+        assertEquals("{\n  hello: 'world',\n  key: 'value',\n}", element.toString(options));
+    }
+    //</editor-fold>
+
+    //<editor-fold desc="Modified by Ultreon (added test for quoteless and comments)">
+    @Test
+    void quotelessAndComment() {
+        Json5Options options = new Json5Options(true, true, true, 2, true);
+        Json5Object element = new Json5Object();
+
+        element.add("hello", Json5Primitive.of("world"));
+        element.add("key", Json5Primitive.of("value"));
+        element.setComment("key", "This is a comment\nAnd another comment");
+
+        assertEquals("{\n  hello: 'world',\n  // This is a comment\n  // And another comment\n  key: 'value',\n}", element.toString(options));
+    }
+    //</editor-fold>
 }
\ No newline at end of file
Index: src/test/java/de/marhali/json5/TestJson5Parser.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/de/marhali/json5/TestJson5Parser.java b/src/test/java/de/marhali/json5/TestJson5Parser.java
--- a/src/test/java/de/marhali/json5/TestJson5Parser.java	(revision d220224342fac20d9636fe37b6379afe2faef53e)
+++ b/src/test/java/de/marhali/json5/TestJson5Parser.java	(date 1754395044728)
@@ -36,7 +36,9 @@
     @Test
     void array() {
         String payload = "['hello',1,'two',{'key':'value'}]";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
         assertEquals(payload, element.toString(options));
@@ -48,7 +50,10 @@
     @Test
     void object() {
         String payload = "{'key':'value','array':['first','second'],'nested':{'key':'value'}}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Object element = Json5Parser.parseObject(lexer);
         assertEquals(payload, element.toString(options));
@@ -59,7 +64,9 @@
     @Test
     void determineArrayType() {
         String payload = "['first','second']";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Element element = Json5Parser.parse(lexer);
         assertTrue(element.isJson5Array());
@@ -69,7 +76,9 @@
     @Test
     void determineObjectType() {
         String payload = "{'key':'value'}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Element element = Json5Parser.parse(lexer);
         assertTrue(element.isJson5Object());
@@ -79,7 +88,9 @@
     @Test
     void hexadecimal() {
         String payload = "{'key':0x100}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Object element = Json5Parser.parseObject(lexer);
         assertEquals(payload, element.toString(options));
@@ -89,7 +100,9 @@
     @Test
     void insideQuotes() {
         String payload = "[\"example\",'other']";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
         assertEquals("['example','other']", element.toString(options));
@@ -98,7 +111,9 @@
     @Test
     void mixedQuotes() {
         String payload = "{ a: \"Test \\' 123\" }";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Object element = Json5Parser.parseObject(lexer);
         assertEquals("Test ' 123", element.get("a").getAsString());
@@ -107,7 +122,9 @@
     @Test
     void escapeChars() {
         String payload = "{ a: \"\\n\\r\\f\\b\\t\\v\\0\u12fa\\x7F\" }";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Object element = Json5Parser.parseObject(lexer);
         assertEquals("\n\r\f\b\t\u000B\0\u12fa\u007F", element.get("a").getAsString());
@@ -117,7 +134,9 @@
     @Test
     void specialNumbers() {
         String payload = "[+NaN,NaN,-NaN,+Infinity,Infinity,-Infinity]";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
         assertEquals("[NaN,NaN,NaN,Infinity,Infinity,-Infinity]", element.toString(options));
@@ -127,7 +146,9 @@
     @Test
     void malformed() {
         String payload = "[10}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parse(lexer));
     }
@@ -135,7 +156,9 @@
     @Test
     void notAObject() {
         String payload = "[]";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parseObject(lexer));
     }
@@ -143,7 +166,9 @@
     @Test
     void incompleteObject() {
         String payload = "{";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parseObject(lexer));
     }
@@ -151,7 +176,9 @@
     @Test
     void notAArray() {
         String payload = "{}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parseArray(lexer));
     }
@@ -159,7 +186,9 @@
     @Test
     void incompleteArray() {
         String payload = "[";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parseArray(lexer));
     }
@@ -167,7 +196,9 @@
     @Test
     void duplicateObjectKeys() {
         String payload = "{'key':'value','key':'value'}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parseObject(lexer));
     }
@@ -175,7 +206,9 @@
     @Test
     void noDivider() {
         String payload = "{'key''value'}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parseObject(lexer));
     }
@@ -183,7 +216,9 @@
     @Test
     void noComma() {
         String payload = "{'key':'value''otherKey':'value'}";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parseObject(lexer));
     }
@@ -191,7 +226,9 @@
     @Test
     void unknownControlCharacter() {
         String payload = "|";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertThrows(Json5Exception.class, () -> Json5Parser.parse(lexer));
     }
@@ -199,7 +236,9 @@
     @Test
     void empty() {
         String payload = "";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         assertNull(Json5Parser.parse(lexer));
     }
@@ -207,7 +246,9 @@
     @Test
     void memberNames() {
         String payload = "{ $Lorem\\u0041_Ipsum123指事字: 0 }";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Object element = Json5Parser.parseObject(lexer);
         assertTrue(element.has("$LoremA_Ipsum123指事字"));
@@ -216,7 +257,9 @@
     @Test
     void multiComments() {
         String payload = "/**/{/**/a/**/:/**/'b'/**/}/**/";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Object element = Json5Parser.parseObject(lexer);
         assertTrue(element.has("a"));
@@ -225,7 +268,9 @@
     @Test
     void singleComments() {
         String payload = "// test\n{ // lorem ipsum\n a: 'b'\n// test\n}// test";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Object element = Json5Parser.parseObject(lexer);
         assertTrue(element.has("a"));
@@ -234,7 +279,9 @@
     @Test
     void booleans() {
         String payload = "[true,false]";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
         assertEquals(payload, element.toString(options));
@@ -244,7 +291,9 @@
     @Test
     void numbers() {
         String payload = "[123e+45,-123e45,123]";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
         assertEquals(123e+45, element.get(0).getAsNumber().doubleValue());
@@ -258,7 +307,9 @@
     @Test
     void nullLiteral() {
         String payload = "[null,{'key':null}]";
-        Json5Options options = new Json5Options(true, true, false, 0);
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        Json5Options options = new Json5Options(true, true, false, 0, false);
+        //</editor-fold>
         Json5Lexer lexer = new Json5Lexer(new StringReader(payload), options);
         Json5Array element = Json5Parser.parseArray(lexer);
         assertEquals(payload, element.toString(options));
Index: src/test/java/de/marhali/json5/TestJson5Writer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/de/marhali/json5/TestJson5Writer.java b/src/test/java/de/marhali/json5/TestJson5Writer.java
--- a/src/test/java/de/marhali/json5/TestJson5Writer.java	(revision d220224342fac20d9636fe37b6379afe2faef53e)
+++ b/src/test/java/de/marhali/json5/TestJson5Writer.java	(date 1757065878176)
@@ -16,6 +16,8 @@
 
 package de.marhali.json5;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 import de.marhali.json5.stream.Json5Writer;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -24,8 +26,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 
-import static org.junit.jupiter.api.Assertions.*;
-
 /**
  * Unit tests for the {@link Json5Writer}.
  *
@@ -33,8 +33,10 @@
  */
 public class TestJson5Writer {
 
+    //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
     private final Json5Options options
-            = new Json5Options(true, true, false, 0);
+            = new Json5Options(true, true, false, 0, false);
+    //</editor-fold>
 
     private StringWriter stringWriter;
     private Json5Writer json5Writer;
@@ -63,7 +65,7 @@
         array.add(123);
         array.add(new Json5Hexadecimal("0x100"));
         array.add("Lorem ipsum");
-        array.add(Json5Null.INSTANCE);
+        array.add(new Json5Null());
         array.add(new Json5Object());
 
         json5Writer.write(array);
@@ -87,6 +89,26 @@
                 stringWriter.toString());
     }
 
+    @Test
+    void objectWithComments() throws IOException {
+        Json5Object object = new Json5Object();
+        object.add("bool", Json5Primitive.of(false));
+        object.add("num", Json5Primitive.of(123));
+        object.add("hex", new Json5Hexadecimal("0x100"));
+        object.add("str", Json5Primitive.of("Lorem ipsum"));
+        object.add("nulled", new Json5Null());
+        object.setComment("nulled", "Null on purpose");
+        object.add("array", new Json5Array());
+
+        json5Writer.write(object);
+
+        assertEquals("""
+{'bool':false,'num':123,'hex':0x100,'str':'Lorem ipsum',// Null on purpose
+'nulled':null,'array':[]}\
+                     """,
+                stringWriter.toString());
+    }
+
     @Test
     void nullLiteral() throws IOException {
         json5Writer.write(Json5Null.INSTANCE);
@@ -140,9 +162,9 @@
     @Test
     void escapeChars() throws IOException {
         Json5Array array = new Json5Array();
-        array.add("\\\n\r\f\b\t\u000B\u12fa\u000b");
+        array.add("\\\n\r\f\b\t\u000Bዺ\u000b");
         json5Writer.write(array);
-        assertEquals("['\\\\\\n\\r\\f\\b\\t\\v\u12fa\\v']", stringWriter.toString());
+        assertEquals("['\\\\\\n\\r\\f\\b\\t\\vዺ\\v']", stringWriter.toString());
         // Cannot test \x7F
     }
 }
Index: src/test/java/de/marhali/json5/TestJson5WriterQuoteless.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/de/marhali/json5/TestJson5WriterQuoteless.java b/src/test/java/de/marhali/json5/TestJson5WriterQuoteless.java
new file mode 100644
--- /dev/null	(date 1757065878289)
+++ b/src/test/java/de/marhali/json5/TestJson5WriterQuoteless.java	(date 1757065878289)
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 Marcel Haßlinger
+ *
+ * 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 de.marhali.json5;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import de.marhali.json5.stream.Json5Writer;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+/**
+ * Unit tests for the {@link Json5Writer}.
+ *
+ * @author Marcel Haßlinger
+ */
+public class TestJson5WriterQuoteless {
+    //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+    private final Json5Options options = new Json5Options(true, true, false, 0, true);
+    //</editor-fold>
+
+    private StringWriter stringWriter;
+
+    private Json5Writer json5Writer;
+
+    @BeforeEach
+    void beforeEach() {
+        stringWriter = new StringWriter();
+        json5Writer = new Json5Writer(options, stringWriter);
+    }
+
+    @Test
+    void quoteEmpty() {
+        assertEquals("''", json5Writer.quote(null));
+        assertEquals("''", json5Writer.quote(""));
+    }
+
+    @Test
+    void quoteEscape() {
+        assertEquals("'\\\\n'", json5Writer.quote("\\n"));
+    }
+
+    @Test
+    void array() throws IOException {
+        Json5Array array = new Json5Array();
+        array.add(true);
+        array.add(123);
+        array.add(new Json5Hexadecimal("0x100"));
+        array.add("Lorem ipsum");
+        array.add(Json5Null.INSTANCE);
+        array.add(new Json5Object());
+
+        json5Writer.write(array);
+
+        //<editor-fold desc="Modified by Ultreon (added support for quoteless)">
+        assertEquals("[true,123,0x100,'Lorem ipsum',null,{}]", stringWriter.toString());
+        //</editor-fold>
+    }
+
+    @Test
+    void object() throws IOException {
+        Json5Object object = new Json5Object();
+        object.add("bool", Json5Primitive.of(false));
+        object.add("num", Json5Primitive.of(123));
+        object.add("hex", new Json5Hexadecimal("0x100"));
+        object.add("str", Json5Primitive.of("Lorem ipsum"));
+        object.add("nulled", Json5Null.INSTANCE);
+        object.add("array", new Json5Array());
+
+        json5Writer.write(object);
+
+        assertEquals("{bool:false,num:123,hex:0x100,str:'Lorem ipsum',nulled:null,array:[]}", stringWriter.toString());
+    }
+
+    //<editor-fold desc="Modified by Ultreon (added support for quoteless and comments)">
+    @Test
+    void objectWithComments() throws IOException {
+        Json5Object object = new Json5Object();
+        object.add("bool", Json5Primitive.of(false));
+        object.add("num", Json5Primitive.of(123));
+        object.add("hex", new Json5Hexadecimal("0x100"));
+        object.add("str", Json5Primitive.of("Lorem ipsum"));
+        object.add("nulled", new Json5Null());
+        object.setComment("nulled", "Null on purpose");
+        object.add("array", new Json5Array());
+
+        json5Writer.write(object);
+
+        assertEquals("""
+                     {bool:false,num:123,hex:0x100,str:'Lorem ipsum',// Null on purpose
+                     nulled:null,array:[]}\
+                     """, stringWriter.toString());
+    }
+    //</editor-fold>
+
+    @Test
+    void nullLiteral() throws IOException {
+        json5Writer.write(Json5Null.INSTANCE);
+        assertEquals("null", stringWriter.toString());
+    }
+
+    @Test
+    void booleans() throws IOException {
+        json5Writer.write(new Json5Boolean(false));
+        assertEquals("false", stringWriter.toString());
+    }
+
+    @Test
+    void largeNumber() throws IOException {
+        json5Writer.write(new Json5Number(123e+45));
+        assertEquals("1.23E47", stringWriter.toString());
+    }
+
+    @Test
+    void largeNumberNegate() throws IOException {
+        json5Writer.write(new Json5Number(-123e+45));
+        assertEquals("-1.23E47", stringWriter.toString());
+    }
+
+    @Test
+    void hexadecimal() throws IOException {
+        json5Writer.write(new Json5Hexadecimal("0x100"));
+        assertEquals("0x100", stringWriter.toString());
+    }
+
+    @Test
+    void number() throws IOException {
+        json5Writer.write(new Json5Number(123));
+        assertEquals("123", stringWriter.toString());
+    }
+
+    @Test
+    void string() throws IOException {
+        json5Writer.write(new Json5String("Lorem ipsum"));
+        assertEquals("'Lorem ipsum'", stringWriter.toString());
+    }
+
+    @Test
+    void memberNames() throws IOException {
+        Json5Object object = new Json5Object();
+        object.addProperty("$LoremA_Ipsum123指事字", 0);
+        json5Writer.write(object);
+        assertEquals("{'$LoremA_Ipsum123指事字':0}", stringWriter.toString());
+    }
+
+    @Test
+    void escapeChars() throws IOException {
+        Json5Array array = new Json5Array();
+        array.add("\\\n\r\f\b\t\u000B\u12fa\u000b");
+        json5Writer.write(array);
+        assertEquals("['\\\\\\n\\r\\f\\b\\t\\v\u12fa\\v']", stringWriter.toString());
+        // Cannot test \x7F
+    }
+}
Index: src/test/resources/expect.comments.json5
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/resources/expect.comments.json5 b/src/test/resources/expect.comments.json5
new file mode 100644
--- /dev/null	(date 1757065878153)
+++ b/src/test/resources/expect.comments.json5	(date 1757065878153)
@@ -0,0 +1,21 @@
+{
+  // The master switch for the feature
+  'enabled': true,
+  // A list of network ports
+  'ports': [
+    // Standard port
+    8001,
+    // Alternate port
+    8002,
+    /**
+     * Admin port
+     * Do not expose publicly
+     */
+    9999
+  ],
+  // User configuration
+  'user': {
+    // The user's login name
+    'name': 'gemini'
+  }
+}
\ No newline at end of file
Index: src/test/resources/expect.quoteless.comment.json5
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/resources/expect.quoteless.comment.json5 b/src/test/resources/expect.quoteless.comment.json5
new file mode 100644
--- /dev/null	(date 1757065878200)
+++ b/src/test/resources/expect.quoteless.comment.json5	(date 1757065878200)
@@ -0,0 +1,31 @@
+// 验证连续单行
+// json5支持quoteless 匹配^[a-zA-Z_][a-zA-Z0-9_]*[a-zA-Z_]$的key可以不加引号
+{
+  // json5 支持尾逗号和注释
+  cards_apd_business_scenario_t: {
+    // 此对象值皆视为字面量
+    '背景数据': {
+      target_table_name: 'cards_ar_invoice_info_t',
+      append_table_name: 'cards_ar_inv_info_apd_t',
+      append_tmp_table_name: 'cards_ar_inv_info_apd_tmp',
+      append_effective_type: 'APPEND_PRIORITY',
+      business_scenario_zh_name: '清关发票补录Jiuli',
+      business_scenario_en_name: 'Append customs invoice No',
+      description: '清关发票数据补录',
+      enabled_flag: 'Y',
+      delete_flag: 'N',
+      creation_date: '2025-03-07 13:39:16.000000',
+      last_updated_by: 174022309561388,
+      last_update_date: '2025-05-06 20:09:57.436000',
+      tenant_id: '11111111111111111111111111111111'
+    },
+    // 以下由程序根据.feature文件自动生成
+    '重点验证': [
+      {
+        created_by: '${created}'
+      }
+    ],
+    // key为字段名 value为缺省值 value为null时表示让程序自动赋值 value为${seq}时表示使用自增主键值
+    '隔离策略': {}
+  }
+}
\ No newline at end of file
Index: src/test/resources/expect.无注释拷贝.json5
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/resources/expect.无注释拷贝.json5 b/src/test/resources/expect.无注释拷贝.json5
new file mode 100644
--- /dev/null	(date 1757065878225)
+++ b/src/test/resources/expect.无注释拷贝.json5	(date 1757065878225)
@@ -0,0 +1,25 @@
+{
+  cards_apd_business_scenario_t: {
+    '背景数据': {
+      target_table_name: 'cards_ar_invoice_info_t',
+      append_table_name: 'cards_ar_inv_info_apd_t',
+      append_tmp_table_name: 'cards_ar_inv_info_apd_tmp',
+      append_effective_type: 'APPEND_PRIORITY',
+      business_scenario_zh_name: '清关发票补录Jiuli',
+      business_scenario_en_name: 'Append customs invoice No',
+      description: '清关发票数据补录',
+      enabled_flag: 'Y',
+      delete_flag: 'N',
+      creation_date: '2025-03-07 13:39:16.000000',
+      last_updated_by: 174022309561388,
+      last_update_date: '2025-05-06 20:09:57.436000',
+      tenant_id: '11111111111111111111111111111111'
+    },
+    '重点验证': [
+      {
+        created_by: '${created}'
+      }
+    ],
+    '隔离策略': {}
+  }
+}
\ No newline at end of file
Index: src/test/resources/test.comments.json5
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/resources/test.comments.json5 b/src/test/resources/test.comments.json5
new file mode 100644
--- /dev/null	(date 1757065878200)
+++ b/src/test/resources/test.comments.json5	(date 1757065878200)
@@ -0,0 +1,21 @@
+{
+  //The master switch for the feature
+  "enabled": true,
+  // A list of network ports
+  "ports": [
+    // Standard port
+    8001,
+       // Alternate port
+    8002,
+        /**
+         * Admin port
+         * Do not expose publicly
+         */
+    9999
+  ],
+  // User configuration
+  "user": {
+    // The user's login name
+    "name": "gemini"
+  }
+}
\ No newline at end of file

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions