From a66f556345829420e000daad632304def61dea4b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 14 Dec 2021 22:35:59 +1300 Subject: [PATCH] Add support for JsonGenerator.Key --- .../eclipse/parsson/JsonGeneratorImpl.java | 25 +++++ .../eclipse/parsson/JsonGeneratorKeyImpl.java | 103 ++++++++++++++++++ .../org/eclipse/parsson/JsonProviderImpl.java | 5 + .../eclipse/parsson/tests/JsonFieldTest.java | 43 +++++++- 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 impl/src/main/java/org/eclipse/parsson/JsonGeneratorKeyImpl.java diff --git a/impl/src/main/java/org/eclipse/parsson/JsonGeneratorImpl.java b/impl/src/main/java/org/eclipse/parsson/JsonGeneratorImpl.java index 63fab88e..9bf98cf0 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonGeneratorImpl.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonGeneratorImpl.java @@ -150,6 +150,18 @@ private JsonGenerator writeName(String name) { return this; } + private void writeName(Key name) { + writeComma(); + final char[] escaped = name.toCharArray(); + final int required = escaped.length; + if (len + required >= buf.length) { + flushBuffer(); + } + System.arraycopy(escaped, 0, buf, len, required); + len += required; + writeColon(); + } + @Override public JsonGenerator write(String name, String fieldValue) { write(name, (CharSequence) fieldValue); @@ -464,6 +476,19 @@ public JsonGenerator writeKey(String name) { return this; } + @Override + public JsonGenerator writeKey(JsonGenerator.Key name) { + if (currentContext.scope != Scope.IN_OBJECT) { + throw new JsonGenerationException( + JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope)); + } + writeName(name); + stack.push(currentContext); + currentContext = new Context(Scope.IN_FIELD); + currentContext.first = false; + return this; + } + @Override public JsonGenerator writeEnd() { if (currentContext.scope == Scope.IN_NONE) { diff --git a/impl/src/main/java/org/eclipse/parsson/JsonGeneratorKeyImpl.java b/impl/src/main/java/org/eclipse/parsson/JsonGeneratorKeyImpl.java new file mode 100644 index 00000000..cd129514 --- /dev/null +++ b/impl/src/main/java/org/eclipse/parsson/JsonGeneratorKeyImpl.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.eclipse.parsson; + +import jakarta.json.stream.JsonGenerator; + +final class JsonGeneratorKeyImpl { + + static JsonGenerator.Key createKey(String name) { + return new Key(quoteEscape(name)); + } + + /** + * A key that is already escaped so then can just be written directly. + */ + private static class Key implements JsonGenerator.Key { + + private final char[] escapedName; + + Key(String escapedName) { + this.escapedName = escapedName.toCharArray(); + } + + @Override + public char[] toCharArray() { + return escapedName; + } + } + + private static String quoteEscape(CharSequence string) { + StringBuilder builder = new StringBuilder(); + builder.append('"'); + int len = string.length(); + for (int i = 0; i < len; i++) { + int begin = i, end = i; + char c = string.charAt(i); + // find all the characters that need not be escaped + // unescaped = %x20-21 | %x23-5B | %x5D-10FFFF + while (c >= 0x20 && c <= 0x10ffff && c != 0x22 && c != 0x5c) { + i++; + end = i; + if (i < len) { + c = string.charAt(i); + } else { + break; + } + } + // Write characters without escaping + if (begin < end) { + builder.append(string, begin, end); + if (i == len) { + break; + } + } + + switch (c) { + case '"': + case '\\': + builder.append('\\'); + builder.append(c); + break; + case '\b': + builder.append('\\'); + builder.append('b'); + break; + case '\f': + builder.append('\\'); + builder.append('f'); + break; + case '\n': + builder.append('\\'); + builder.append('n'); + break; + case '\r': + builder.append('\\'); + builder.append('r'); + break; + case '\t': + builder.append('\\'); + builder.append('t'); + break; + default: + String hex = "000" + Integer.toHexString(c); + builder.append("\\u" + hex.substring(hex.length() - 4)); + } + } + return builder.append('"').toString(); + } +} diff --git a/impl/src/main/java/org/eclipse/parsson/JsonProviderImpl.java b/impl/src/main/java/org/eclipse/parsson/JsonProviderImpl.java index 57af12cd..6e51d5cc 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonProviderImpl.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonProviderImpl.java @@ -44,6 +44,11 @@ public class JsonProviderImpl extends JsonProvider { private final BufferPool bufferPool = new BufferPoolImpl(); + @Override + public JsonGenerator.Key createGeneratorKey(String name) { + return JsonGeneratorKeyImpl.createKey(name); + } + @Override public JsonGenerator createGenerator(Writer writer) { return new JsonGeneratorImpl(writer, bufferPool); diff --git a/impl/src/test/java/org/eclipse/parsson/tests/JsonFieldTest.java b/impl/src/test/java/org/eclipse/parsson/tests/JsonFieldTest.java index 476c9cc9..2fab985b 100644 --- a/impl/src/test/java/org/eclipse/parsson/tests/JsonFieldTest.java +++ b/impl/src/test/java/org/eclipse/parsson/tests/JsonFieldTest.java @@ -16,13 +16,12 @@ package org.eclipse.parsson.tests; +import jakarta.json.spi.JsonProvider; import junit.framework.TestCase; import jakarta.json.Json; -import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonValue; import jakarta.json.stream.JsonGenerationException; import jakarta.json.stream.JsonGenerator; import java.io.StringWriter; @@ -37,6 +36,46 @@ */ public class JsonFieldTest extends TestCase { + public void testKeyFieldObject() { + StringWriter sw = new StringWriter(); + JsonProvider provider = JsonProvider.provider(); + + JsonGenerator.Key f1Name = provider.createGeneratorKey("f1Name"); + JsonGenerator.Key innerFieldName = provider.createGeneratorKey("innerFieldName"); + JsonGenerator.Key f2Name = provider.createGeneratorKey("f2Name"); + + JsonGenerator generator = provider.createGenerator(sw); + + generator.writeStartObject(); + generator.writeKey(f1Name); + generator.writeStartObject(); + generator.writeKey(innerFieldName).write("innerFieldValue"); + generator.writeEnd(); + generator.writeKey(f2Name).write("f2Value"); + generator.writeEnd(); + + generator.close(); + assertEquals("{\"f1Name\":{\"innerFieldName\":\"innerFieldValue\"},\"f2Name\":\"f2Value\"}", sw.toString()); + } + + public void testKeyFieldEscaped() { + StringWriter sw = new StringWriter(); + JsonProvider provider = JsonProvider.provider(); + + JsonGenerator.Key f1Name = provider.createGeneratorKey("\"f1Name\""); + JsonGenerator.Key f2Name = provider.createGeneratorKey("f2-name"); + + JsonGenerator generator = provider.createGenerator(sw); + + generator.writeStartObject(); + generator.writeKey(f1Name).write("a"); + generator.writeKey(f2Name).write("b"); + generator.writeEnd(); + + generator.close(); + assertEquals("{\"\\\"f1Name\\\"\":\"a\",\"f2-name\":\"b\"}", sw.toString()); + } + public void testFieldAsOnlyMember() { StringWriter sw = new StringWriter(); JsonGenerator generator = Json.createGenerator(sw);