This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/json-key
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit c95f0e7750739135a7dae15eceabd435fbf87d3a
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon May 25 14:08:31 2026 +0100

    Improve JSON key parsing
---
 .../json/basic/JsonMapObjectReaderWriter.java      | 46 +++++++++++++++++++++-
 .../json/basic/JsonMapObjectReaderWriterTest.java  | 27 +++++++++++++
 2 files changed, 71 insertions(+), 2 deletions(-)

diff --git 
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
 
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
index f6c8abe4145..bbd6852302f 100644
--- 
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
+++ 
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
@@ -190,9 +190,10 @@ public class JsonMapObjectReaderWriter {
                 continue;
             }
 
-            int closingQuote = json.indexOf(DQUOTE, i + 1);
+            int closingQuote = json.charAt(i) == DQUOTE
+                    ? findClosingQuote(json, i) : json.indexOf(DQUOTE, i + 1);
             int from = json.charAt(i) == DQUOTE ? i + 1 : i;
-            String name = json.substring(from, closingQuote);
+            String name = unescapeKeyName(json.substring(from, closingQuote));
             int sepIndex = json.indexOf(COLON, closingQuote + 1);
             if (sepIndex == -1) {
                 throw new UncheckedIOException(new IOException("Error in 
parsing json"));
@@ -398,6 +399,47 @@ public class JsonMapObjectReaderWriter {
 
     }
 
+    /**
+     * Returns the index of the closing {@code "} that matches the opening 
quote at
+     * {@code openQuoteIndex}, correctly skipping over escaped quotes ({@code 
\"}) and
+     * escaped backslashes ({@code \\}) inside the string by counting 
consecutive
+     * backslashes immediately before each candidate {@code "}: an odd count 
means the
+     * quote is escaped; an even count means it is a real string delimiter.
+     */
+    private static int findClosingQuote(String json, int openQuoteIndex) {
+        for (int i = openQuoteIndex + 1; i < json.length(); i++) {
+            if (json.charAt(i) == DQUOTE) {
+                int backslashCount = 0;
+                int k = i - 1;
+                while (k > openQuoteIndex && json.charAt(k) == ESCAPE) {
+                    backslashCount++;
+                    k--;
+                }
+                if (backslashCount % 2 == 0) {
+                    return i;
+                }
+            }
+        }
+        return json.length(); // malformed — treat end-of-string as sentinel
+    }
+
+    /**
+     * Decodes the JSON escape sequences that may appear in a key name:
+     * {@code \/} → {@code /}, {@code \"} → {@code "}, {@code \\} → {@code \}.
+     */
+    private static String unescapeKeyName(String name) {
+        if (name.contains("\\/")) {
+            name = name.replace("\\/", "/");
+        }
+        if (name.contains("\\\"")) {
+            name = name.replace("\\\"", "\"");
+        }
+        if (name.contains("\\\\")) {
+            name = name.replace("\\\\", "\\");
+        }
+        return name;
+    }
+
     private String escapeJson(String value) {
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < value.length(); i++) {
diff --git 
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
 
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
index c2908b1a75d..a680ecba04d 100644
--- 
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
+++ 
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
@@ -239,6 +239,33 @@ public class JsonMapObjectReaderWriterTest {
         assertEquals(Boolean.TRUE, map.get("admin"));
     }
 
+    /**
+     * Regression test for "Key Names With Escaped Quotes Parsed Incorrectly".
+     *
+     * <p>{@code readJsonObjectAsSettable} extracts a key name with a plain
+     * {@code json.indexOf(DQUOTE, i + 1)}, which stops at the first {@code "} 
it finds
+     * regardless of whether that quote is escaped.  A key that contains an 
embedded
+     * escaped quote — e.g. {@code "foo\"bar"} — is therefore truncated: the 
method
+     * finds the {@code "} in {@code \"} and returns {@code foo\} instead of
+     * {@code foo"bar}.
+     *
+     * <p>The parser then searches for the value separator {@code :} starting 
from the
+     * wrong offset, so the remainder of the key ({@code bar}) and the colon 
are
+     * consumed as a suffix of the (wrong) key name.  The resulting map 
contains an
+     * entry with the wrong key and the test assertion on {@code 
map.get("foo\"bar")}
+     * returns {@code null}.
+     */
+    @Test
+    public void testKeyWithEscapedQuoteIsParsedCorrectly() throws Exception {
+        // JSON: {"foo\"bar":"value"}  — key contains an embedded double-quote 
character
+        // Bug: indexOf('"') stops at the \" inside the key, producing 
truncated key "foo\"
+        // instead of the correct key foo"bar.
+        String json = "{\"foo\\\"bar\":\"value\"}";
+        Map<String, Object> map = new 
JsonMapObjectReaderWriter().fromJson(json);
+        assertEquals(1, map.size());
+        assertEquals("value", map.get("foo\"bar"));
+    }
+
     @Test
     public void testAlreadyEscapedBackslash() throws Exception {
         JsonMapObjectReaderWriter jsonMapObjectReaderWriter = new 
JsonMapObjectReaderWriter();

Reply via email to