1
0
mirror of https://github.com/TeamNewPipe/nanojson synced 2025-10-06 00:13:15 +02:00

Merge pull request #27 from TeamNewPipe/revert-our-changes-and-use-firemasterk

This commit is contained in:
Stypox
2025-10-04 14:15:26 +02:00
committed by GitHub
24 changed files with 1947 additions and 1570 deletions

View File

@@ -1,54 +0,0 @@
name: "Code scanning - action"
on:
push:
branches: [master, ]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 11 * * 5'
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

31
.github/workflows/maven.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Java CI with Maven
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml

View File

@@ -2,6 +2,5 @@
This is a fork of [nanojson](https://github.com/mmastrac/nanojson) for use by NewPipe(Extractor). It has the following changes: This is a fork of [nanojson](https://github.com/mmastrac/nanojson) for use by NewPipe(Extractor). It has the following changes:
- It returns an empty `JsonObject` or `JsonArray` by default instead of `null`, preventing `NullPointerException`s.
- It accepts JS-like JSON, such as `{ this: 'is', an: 'example' }`.
- Added ``JsonArray#streamAs`` and ``JsonArray#streamAsJsonObjects`` shortcut methods. - Added ``JsonArray#streamAs`` and ``JsonArray#streamAsJsonObjects`` shortcut methods.
- Various performance improvements borrowed from [@FireMasterK's fork](https://github.com/FireMasterK/nanojson).

View File

@@ -20,8 +20,7 @@
</module> </module>
<module name="LineLength"> <module name="LineLength">
<property name="max" value="120" /> <property name="max" value="150" />
<property name="tabWidth" value="4" />
</module> </module>
<!-- If you set the basedir property below, then all reported file names <!-- If you set the basedir property below, then all reported file names
@@ -70,7 +69,7 @@
<!-- Checks for Javadoc comments. --> <!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html --> <!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod"> <module name="JavadocMethod">
<property name="scope" value="public" /> <property name="accessModifiers" value="public" />
<property name="allowMissingParamTags" value="true" /> <property name="allowMissingParamTags" value="true" />
<property name="allowMissingReturnTag" value="true" /> <property name="allowMissingReturnTag" value="true" />
</module> </module>

95
pom.xml
View File

@@ -13,19 +13,35 @@
<artifactId>nanojson</artifactId> <artifactId>nanojson</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>nanojson</name> <name>nanojson</name>
<version>1.8-SNAPSHOT</version> <version>1.11-SNAPSHOT</version>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties> </properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.randelshofer</groupId>
<artifactId>fastdoubleparser</artifactId>
<version>2.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<reporting> <reporting>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version> <version>3.11.3</version>
<reportSets> <reportSets>
<reportSet> <reportSet>
<id>html</id> <id>html</id>
@@ -44,7 +60,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version> <version>3.6.0</version>
<configuration> <configuration>
<configLocation>checkstyle.xml</configLocation> <configLocation>checkstyle.xml</configLocation>
<propertyExpansion>basedir=${basedir}</propertyExpansion> <propertyExpansion>basedir=${basedir}</propertyExpansion>
@@ -66,7 +82,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId> <artifactId>maven-surefire-report-plugin</artifactId>
<version>2.22.2</version> <version>3.5.3</version>
<reportSets> <reportSets>
<reportSet> <reportSet>
<reports> <reports>
@@ -77,21 +93,12 @@
</plugin> </plugin>
</plugins> </plugins>
</reporting> </reporting>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version> <version>3.3.1</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
@@ -104,7 +111,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version> <version>3.11.3</version>
<executions> <executions>
<execution> <execution>
<id>attach-javadocs</id> <id>attach-javadocs</id>
@@ -117,7 +124,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version> <version>3.6.0</version>
<configuration> <configuration>
<configLocation>checkstyle.xml</configLocation> <configLocation>checkstyle.xml</configLocation>
<propertyExpansion>basedir=${basedir}</propertyExpansion> <propertyExpansion>basedir=${basedir}</propertyExpansion>
@@ -145,7 +152,7 @@
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId> <artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version> <version>3.5.1</version>
<configuration> <configuration>
<executable>java</executable> <executable>java</executable>
@@ -165,15 +172,10 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <version>3.14.0</version>
<configuration> <configuration>
<debug>true</debug> <debug>false</debug>
<debuglevel>none</debuglevel> <release>11</release>
<source>1.8</source>
<target>1.8</target>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
<compilerArgument>-Xlint:all</compilerArgument> <compilerArgument>-Xlint:all</compilerArgument>
<compilerArguments> <compilerArguments>
<Werror /> <Werror />
@@ -183,20 +185,43 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId> <artifactId>maven-site-plugin</artifactId>
<version>3.9.1</version> <version>3.21.0</version>
<configuration> <configuration>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>biz.aQute.bnd</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>bnd-maven-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration> <configuration>
<archive> <bnd><![CDATA[
<manifestEntries> Bundle-SymbolicName: ${groupId}.${artifactId}
<Automatic-Module-Name>com.grack.nanojson</Automatic-Module-Name> Export-Package: com.grack.nanojson
</manifestEntries> -jpms-module-info: com.grack.nanojson
</archive> -noextraheaders:
-removeheaders: \
Tool, \
Bnd-LastModified, \
Bnd-ManifestVersion, \
Build-Jdk, \
Built-By, \
Created-By, \
Private-Package, \
Bundle-DocURL, \
Bundle-Name, \
Bundle-Vendor,\
Bundle-Description,\
Bundle-SCM
]]></bnd>
</configuration> </configuration>
</execution>
</executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
@@ -210,7 +235,7 @@
<plugin> <plugin>
<groupId>org.sonatype.plugins</groupId> <groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId> <artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version> <version>1.7.0</version>
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<serverId>sonatype-nexus-staging</serverId> <serverId>sonatype-nexus-staging</serverId>
@@ -221,7 +246,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId> <artifactId>maven-gpg-plugin</artifactId>
<version>3.0.1</version> <version>3.2.8</version>
<executions> <executions>
<execution> <execution>
<id>sign-artifacts</id> <id>sign-artifacts</id>

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2011 The nanojson Authors
*
* 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 com.grack.nanojson;
import java.nio.CharBuffer;
import java.util.PriorityQueue;
import java.util.concurrent.atomic.AtomicInteger;
public final class CharBufferPool {
private static final AtomicInteger SIZE = new AtomicInteger(0);
private static final int MAX_SIZE = 1000;
private static final int MAX_RETAINED_BUFFER_SIZE = 16 * 1024; // 16KB limit for pooled buffers
private static final PriorityQueue<CharBuffer> BUFFERS = new PriorityQueue<>();
private CharBufferPool() {
}
public static CharBuffer get(int capacity) {
synchronized (BUFFERS) {
if (!BUFFERS.isEmpty()) {
CharBuffer buffer = BUFFERS.poll();
if (buffer.capacity() < capacity) {
return CharBuffer.allocate(capacity);
}
return buffer;
}
}
if (SIZE.incrementAndGet() > MAX_SIZE) {
SIZE.decrementAndGet();
throw new IllegalStateException("Buffer pool size limit exceeded");
}
return CharBuffer.allocate(capacity);
}
public static void release(CharBuffer buffer) {
if (buffer == null || buffer.capacity() <= 0) {
return;
}
if (buffer.limit() > MAX_RETAINED_BUFFER_SIZE) {
// If the buffer is too large, decrement SIZE so another can be created
SIZE.decrementAndGet();
return;
}
buffer.clear();
synchronized (BUFFERS) {
BUFFERS.add(buffer);
}
}
}

View File

@@ -215,6 +215,8 @@ public class JsonArray extends ArrayList<Object> {
*/ */
public String getString(int key, String default_) { public String getString(int key, String default_) {
Object o = get(key); Object o = get(key);
if (o instanceof LazyString)
return o.toString();
if (o instanceof String) if (o instanceof String)
return (String) o; return (String) o;
return default_; return default_;
@@ -252,7 +254,8 @@ public class JsonArray extends ArrayList<Object> {
* Returns true if the array has a string element at that index. * Returns true if the array has a string element at that index.
*/ */
public boolean isString(int key) { public boolean isString(int key) {
return get(key) instanceof String; Object o = get(key);
return o instanceof LazyString || o instanceof String;
} }
/** /**

View File

@@ -27,6 +27,7 @@ import java.util.Stack;
*/ */
public final class JsonBuilder<T> implements JsonSink<JsonBuilder<T>> { public final class JsonBuilder<T> implements JsonSink<JsonBuilder<T>> {
private Stack<Object> json = new Stack<>(); private Stack<Object> json = new Stack<>();
private String pendingKey;
private T root; private T root;
JsonBuilder(T root) { JsonBuilder(T root) {
@@ -73,12 +74,20 @@ public final class JsonBuilder<T> implements JsonSink<JsonBuilder<T>> {
@Override @Override
public JsonBuilder<T> value(Object o) { public JsonBuilder<T> value(Object o) {
if (pendingKey != null) {
obj().put(pendingKey, o);
pendingKey = null;
} else {
arr().add(o); arr().add(o);
}
return this; return this;
} }
@Override @Override
public JsonBuilder<T> value(String key, Object o) { public JsonBuilder<T> value(String key, Object o) {
if (pendingKey != null) {
throw new JsonWriterException("Invalid call to emit a key value immediately after emitting a key");
}
obj().put(key, o); obj().put(key, o);
return this; return this;
} }
@@ -193,6 +202,18 @@ public final class JsonBuilder<T> implements JsonSink<JsonBuilder<T>> {
return this; return this;
} }
@Override
public JsonBuilder<T> key(String key) {
if (key == null)
throw new NullPointerException("key");
if (!(json.peek() instanceof JsonObject))
throw new JsonWriterException("Invalid call to emit a key value while not writing an object");
if (pendingKey != null)
throw new JsonWriterException("Invalid call to emit a key value immediately after emitting a key");
pendingKey = key;
return this;
}
private JsonObject obj() { private JsonObject obj() {
try { try {
return (JsonObject)json.peek(); return (JsonObject)json.peek();

View File

@@ -0,0 +1,13 @@
package com.grack.nanojson;
/**
* An interface for classes that can be converted into valid JSON values.
*/
public interface JsonConvertible {
/**
* Creates a view of this object as a valid JSON Type.
*
* @return an instance of Map, Collection, String, Number or Boolean or {@code null}
*/
Object toJsonValue();
}

View File

@@ -15,45 +15,52 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import java.math.BigDecimal; import ch.randelshofer.fastdoubleparser.JavaBigDecimalParser;
import ch.randelshofer.fastdoubleparser.JavaDoubleParser;
import ch.randelshofer.fastdoubleparser.JavaFloatParser;
/** /**
* Lazily-parsed number for performance. * Lazily-parsed number for performance.
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
class JsonLazyNumber extends Number { class JsonLazyNumber extends Number {
private String value; private char[] value;
private boolean isDouble; private boolean isDouble;
JsonLazyNumber(String number, boolean isDoubleValue) { JsonLazyNumber(char[] number, boolean isDoubleValue) {
this.value = number; this.value = number;
this.isDouble = isDoubleValue; this.isDouble = isDoubleValue;
} }
@Override @Override
public double doubleValue() { public double doubleValue() {
return Double.parseDouble(value); return JavaDoubleParser.parseDouble(value);
} }
@Override @Override
public float floatValue() { public float floatValue() {
return Float.parseFloat(value); return JavaFloatParser.parseFloat(value);
} }
@Override @Override
public int intValue() { public int intValue() {
return isDouble ? (int)Double.parseDouble(value) : Integer.parseInt(value); return isDouble ? (int)JavaDoubleParser.parseDouble(value) : Integer.parseInt(new String(value));
} }
@Override @Override
public long longValue() { public long longValue() {
return isDouble ? (long)Double.parseDouble(value) : Long.parseLong(value); return isDouble ? (long) JavaDoubleParser.parseDouble(value) : Long.parseLong(new String(value));
}
@Override
public String toString() {
return new String(value);
} }
/** /**
* Avoid serializing {@link JsonLazyNumber}. * Avoid serializing {@link JsonLazyNumber}.
*/ */
private Object writeReplace() { private Object writeReplace() {
return new BigDecimal(value); return JavaBigDecimalParser.parseBigDecimal(value);
} }
} }

View File

@@ -206,6 +206,8 @@ public class JsonObject extends LinkedHashMap<String, Object> {
*/ */
public String getString(String key, String default_) { public String getString(String key, String default_) {
Object o = get(key); Object o = get(key);
if (o instanceof LazyString)
return o.toString();
if (o instanceof String) if (o instanceof String)
return (String) o; return (String) o;
return default_; return default_;
@@ -243,6 +245,7 @@ public class JsonObject extends LinkedHashMap<String, Object> {
* Returns true if the object has a string element at that key. * Returns true if the object has a string element at that key.
*/ */
public boolean isString(String key) { public boolean isString(String key) {
return get(key) instanceof String; Object o = get(key);
return o instanceof LazyString || o instanceof String;
} }
} }

View File

@@ -15,12 +15,15 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import ch.randelshofer.fastdoubleparser.JavaBigIntegerParser;
import ch.randelshofer.fastdoubleparser.JavaDoubleParser;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.math.BigInteger;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
/** /**
* Simple JSON parser. * Simple JSON parser.
@@ -36,18 +39,21 @@ public final class JsonParser {
private Object value; private Object value;
private int token; private int token;
private JsonTokener tokener; private final JsonTokener tokener;
private boolean lazyNumbers; private final boolean lazyNumbers;
private final boolean lazyStrings;
/** /**
* Returns a type-safe parser context for a {@link JsonObject}, {@link JsonArray} or "any" type from which you can * Returns a type-safe parser context for a {@link JsonObject},
* {@link JsonArray} or "any" type from which you can
* parse a {@link String} or a {@link Reader}. * parse a {@link String} or a {@link Reader}.
* *
* @param <T> The parsed type. * @param <T> The parsed type.
*/ */
public static final class JsonParserContext<T> { public static final class JsonParserContext<T> {
private final Class<T> clazz; private final Class<T> clazz;
private boolean lazyNumbers; private boolean lazyNumbers = true;
private boolean lazyStrings = true;
JsonParserContext(Class<T> clazz) { JsonParserContext(Class<T> clazz) {
this.clazz = clazz; this.clazz = clazz;
@@ -62,18 +68,27 @@ public final class JsonParser {
return this; return this;
} }
/**
* Parses Strings lazily, allowing us to defer some of the cost of String
* construction until later.
*/
public JsonParserContext<T> withLazyStrings() {
lazyStrings = true;
return this;
}
/** /**
* Parses the current JSON type from a {@link String}. * Parses the current JSON type from a {@link String}.
*/ */
public T from(String s) throws JsonParserException { public T from(String s) throws JsonParserException {
return new JsonParser(new JsonTokener(new StringReader(s)), lazyNumbers).parse(clazz); return new JsonParser(new JsonTokener(new StringReader(s)), lazyNumbers, lazyStrings).parse(clazz);
} }
/** /**
* Parses the current` JSON type from a {@link Reader}. * Parses the current` JSON type from a {@link Reader}.
*/ */
public T from(Reader r) throws JsonParserException { public T from(Reader r) throws JsonParserException {
return new JsonParser(new JsonTokener(r), lazyNumbers).parse(clazz); return new JsonParser(new JsonTokener(r), lazyNumbers, lazyStrings).parse(clazz);
} }
/** /**
@@ -81,11 +96,8 @@ public final class JsonParser {
*/ */
public T from(URL url) throws JsonParserException { public T from(URL url) throws JsonParserException {
try { try {
InputStream stm = url.openStream(); try (InputStream stm = url.openStream()) {
try {
return from(stm); return from(stm);
} finally {
stm.close();
} }
} catch (IOException e) { } catch (IOException e) {
throw new JsonParserException(e, "IOException opening URL", 1, 1, 0); throw new JsonParserException(e, "IOException opening URL", 1, 1, 0);
@@ -93,16 +105,18 @@ public final class JsonParser {
} }
/** /**
* Parses the current JSON type from a {@link InputStream}. Detects the encoding from the input stream. * Parses the current JSON type from a {@link InputStream}. Detects the encoding
* from the input stream.
*/ */
public T from(InputStream stm) throws JsonParserException { public T from(InputStream stm) throws JsonParserException {
return new JsonParser(new JsonTokener(stm), lazyNumbers).parse(clazz); return new JsonParser(new JsonTokener(stm), lazyNumbers, lazyStrings).parse(clazz);
} }
} }
JsonParser(JsonTokener tokener, boolean lazyNumbers) throws JsonParserException { JsonParser(JsonTokener tokener, boolean lazyNumbers, boolean lazyStrings) throws JsonParserException {
this.tokener = tokener; this.tokener = tokener;
this.lazyNumbers = lazyNumbers; this.lazyNumbers = lazyNumbers;
this.lazyStrings = lazyStrings;
} }
/** /**
@@ -128,8 +142,10 @@ public final class JsonParser {
} }
/** /**
* Parses any object from a source. For any valid JSON, returns either a null (for the JSON string 'null'), a * Parses any object from a source. For any valid JSON, returns either a null
* {@link String}, a {@link Number}, a {@link Boolean}, a {@link JsonObject} or a {@link JsonArray}. * (for the JSON string 'null'), a
* {@link String}, a {@link Number}, a {@link Boolean}, a {@link JsonObject} or
* a {@link JsonArray}.
* *
* <pre> * <pre>
* Object json = {@link JsonParser}.any().from("{\"a\":[true,false], \"b\":1}"); * Object json = {@link JsonParser}.any().from("{\"a\":[true,false], \"b\":1}");
@@ -144,82 +160,77 @@ public final class JsonParser {
* Parse a single JSON value from the string, expecting an EOF at the end. * Parse a single JSON value from the string, expecting an EOF at the end.
*/ */
<T> T parse(Class<T> clazz) throws JsonParserException { <T> T parse(Class<T> clazz) throws JsonParserException {
advanceToken(false, false); try {
advanceToken();
Object parsed = currentValue(); Object parsed = currentValue();
if (advanceToken(false, false) != JsonTokener.TOKEN_EOF) if (advanceToken() != JsonTokener.TOKEN_EOF)
throw tokener.createParseException(null, "Expected end of input, got " + token, true); throw tokener.createParseException(null, "Expected end of input, got " + token, true);
if (clazz != Object.class && (parsed == null || !clazz.isAssignableFrom(parsed.getClass()))) if (clazz != Object.class && (parsed == null || !clazz.isAssignableFrom(parsed.getClass())))
throw tokener.createParseException(null, throw tokener.createParseException(null,
"JSON did not contain the correct type, expected " + clazz.getSimpleName() + ".", "JSON did not contain the correct type, expected " + clazz.getSimpleName() + ".",
true); true);
return clazz.cast(parsed); return clazz.cast(parsed);
} finally {
// Automatically close the tokener to release resources back to the pool
try {
tokener.close();
} catch (IOException e) {
// Log or ignore IOException during cleanup - don't mask the original exception
}
}
} }
/** /**
* Starts parsing a JSON value at the current token position. * Starts parsing a JSON value at the current token position.
*/ */
private Object currentValue() throws JsonParserException { private Object currentValue() throws JsonParserException {
// Only a value start token should appear when we're in the context of parsing a JSON value // Only a value start token should appear when we're in the context of parsing a
// JSON value
if (token >= JsonTokener.TOKEN_VALUE_MIN) if (token >= JsonTokener.TOKEN_VALUE_MIN)
return value; return value;
throw tokener.createParseException(null, "Expected JSON value, got " + token, true); throw tokener.createParseException(null, "Expected JSON value, got " + token, true);
} }
/** /**
* Consumes a token, first eating up any whitespace ahead of it. Note that number tokens are not necessarily valid * Consumes a token, first eating up any whitespace ahead of it. Note that
* number tokens are not necessarily valid
* numbers. * numbers.
*/ */
private int advanceToken(boolean allowSemiString, boolean old) throws JsonParserException { private int advanceToken() throws JsonParserException {
if (old) tokener.index--; token = tokener.advanceToToken();
token = tokener.advanceToToken(allowSemiString);
switch (token) { switch (token) {
case JsonTokener.TOKEN_ARRAY_START: // Inlined function to avoid additional stack case JsonTokener.TOKEN_ARRAY_START: // Inlined function to avoid additional stack
JsonArray list = new JsonArray(); JsonArray list = new JsonArray();
if (advanceToken(false, false) != JsonTokener.TOKEN_ARRAY_END) if (advanceToken() != JsonTokener.TOKEN_ARRAY_END)
while (true) { while (true) {
list.add(currentValue()); list.add(currentValue());
if (token == JsonTokener.TOKEN_SEMI_STRING) if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
throw tokener.createParseException(null, "Semi-string is not allowed in array", true);
if (advanceToken(false, false) == JsonTokener.TOKEN_ARRAY_END)
break; break;
if (token != JsonTokener.TOKEN_COMMA) if (token != JsonTokener.TOKEN_COMMA)
throw tokener.createParseException(null, throw tokener.createParseException(null,
"Expected a comma or end of the array instead of " + token, true); "Expected a comma or end of the array instead of " + token, true);
if (advanceToken(false, false) == JsonTokener.TOKEN_ARRAY_END) if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
throw tokener.createParseException(null, "Trailing comma found in array", true); throw tokener.createParseException(null, "Trailing comma found in array", true);
} }
value = list; value = list;
return token = JsonTokener.TOKEN_ARRAY_START; return token = JsonTokener.TOKEN_ARRAY_START;
case JsonTokener.TOKEN_OBJECT_START: // Inlined function to avoid additional stack case JsonTokener.TOKEN_OBJECT_START: // Inlined function to avoid additional stack
JsonObject map = new JsonObject(); JsonObject map = new JsonObject();
if (advanceToken(true, false) != JsonTokener.TOKEN_OBJECT_END) if (advanceToken() != JsonTokener.TOKEN_OBJECT_END)
while (true) { while (true) {
switch (token) { if (token != JsonTokener.TOKEN_STRING)
case JsonTokener.TOKEN_NULL:
case JsonTokener.TOKEN_TRUE:
case JsonTokener.TOKEN_FALSE:
value = value.toString();
break;
case JsonTokener.TOKEN_STRING:
case JsonTokener.TOKEN_SEMI_STRING:
break;
default:
throw tokener.createParseException(null, "Expected STRING, got " + token, true); throw tokener.createParseException(null, "Expected STRING, got " + token, true);
} String key = lazyStrings ? value.toString() : (String) value;
String key = (String)value; if (advanceToken() != JsonTokener.TOKEN_COLON)
if (token == JsonTokener.TOKEN_SEMI_STRING) {
if (advanceToken(false, true) != JsonTokener.TOKEN_COLON)
throw tokener.createParseException(null, "Expected COLON, got " + token, true); throw tokener.createParseException(null, "Expected COLON, got " + token, true);
} else if (advanceToken(false, false) != JsonTokener.TOKEN_COLON) advanceToken();
throw tokener.createParseException(null, "Expected COLON, got " + token, true);
advanceToken(false, false);
map.put(key, currentValue()); map.put(key, currentValue());
if (advanceToken(false, false) == JsonTokener.TOKEN_OBJECT_END) if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
break; break;
if (token != JsonTokener.TOKEN_COMMA) if (token != JsonTokener.TOKEN_COMMA)
throw tokener.createParseException(null, throw tokener.createParseException(null,
"Expected a comma or end of the object instead of " + token, true); "Expected a comma or end of the object instead of " + token, true);
if (advanceToken(true, false) == JsonTokener.TOKEN_OBJECT_END) if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
throw tokener.createParseException(null, "Trailing object found in array", true); throw tokener.createParseException(null, "Trailing object found in array", true);
} }
value = map; value = map;
@@ -234,12 +245,15 @@ public final class JsonParser {
value = null; value = null;
break; break;
case JsonTokener.TOKEN_STRING: case JsonTokener.TOKEN_STRING:
case JsonTokener.TOKEN_SEMI_STRING: char[] chars = tokener.reusableBuffer.array();
value = tokener.reusableBuffer.toString(); chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
value = lazyStrings ? new LazyString(chars) : new String(chars);
break; break;
case JsonTokener.TOKEN_NUMBER: case JsonTokener.TOKEN_NUMBER:
if (lazyNumbers) { if (lazyNumbers) {
value = new JsonLazyNumber(tokener.reusableBuffer.toString(), tokener.isDouble); chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
value = new JsonLazyNumber(chars, tokener.isDouble);
} else { } else {
value = parseNumber(); value = parseNumber();
} }
@@ -251,31 +265,33 @@ public final class JsonParser {
} }
private Number parseNumber() throws JsonParserException { private Number parseNumber() throws JsonParserException {
String number = tokener.reusableBuffer.toString(); char[] number = tokener.reusableBuffer.array();
number = Arrays.copyOf(number, tokener.reusableBuffer.position());
int numLength = number.length;
try { try {
if (tokener.isDouble) if (tokener.isDouble)
return Double.parseDouble(number); return JavaDoubleParser.parseDouble(number);
// Quick parse for single-digits // Quick parse for single-digits
if (number.length() == 1) { if (numLength == 1) {
return number.charAt(0) - '0'; return number[0] - '0';
} else if (number.length() == 2 && number.charAt(0) == '-') { } else if (numLength == 2 && number[0] == '-') {
return '0' - number.charAt(1); return '0' - number[1];
} }
// HACK: Attempt to parse using the approximate best type for this // HACK: Attempt to parse using the approximate best type for this
boolean firstMinus = number.charAt(0) == '-'; boolean firstMinus = number[0] == '-';
int length = firstMinus ? number.length() - 1 : number.length(); int length = firstMinus ? numLength - 1 : numLength;
// CHECKSTYLE_OFF: MagicNumber // CHECKSTYLE_OFF: MagicNumber
if (length < 10 || (length == 10 && number.charAt(firstMinus ? 1 : 0) < '2')) // 2 147 483 647 if (length < 10 || (length == 10 && number[firstMinus ? 1 : 0] < '2')) // 2 147 483 647
return Integer.parseInt(number); return Integer.parseInt(new String(number));
if (length < 19 || (length == 19 && number.charAt(firstMinus ? 1 : 0) < '9')) // 9 223 372 036 854 775 807 if (length < 19 || (length == 19 && number[firstMinus ? 1 : 0] < '9')) // 9 223 372 036 854 775 807
return Long.parseLong(number); return Long.parseLong(new String(number));
// CHECKSTYLE_ON: MagicNumber // CHECKSTYLE_ON: MagicNumber
return new BigInteger(number); return JavaBigIntegerParser.parseBigInteger(number);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw tokener.createParseException(e, "Malformed number: " + number, true); throw tokener.createParseException(e, "Malformed number: " + new String(number), true);
} }
} }
} }

View File

@@ -15,22 +15,31 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import ch.randelshofer.fastdoubleparser.JavaDoubleParser;
import ch.randelshofer.fastdoubleparser.JavaFloatParser;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.nio.CharBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.BitSet; import java.util.BitSet;
/** /**
* Streaming reader for JSON documents. * Streaming reader for JSON documents.
*/ */
public final class JsonReader { public final class JsonReader implements Closeable {
private JsonTokener tokener; private JsonTokener tokener;
private int token; private int token;
private BitSet states = new BitSet(); private BitSet states = new BitSet();
private int stateIndex = 0; private int stateIndex = 0;
private boolean inObject; private boolean inObject;
private boolean first = true; private boolean first = true;
private StringBuilder key = new StringBuilder(); // CHECKSTYLE_OFF: MagicNumber
private CharBuffer key = CharBufferPool.get(1024);
// CHECKSTYLE_ON: MagicNumber
/** /**
* The type of value that the {@link JsonReader} is positioned over. * The type of value that the {@link JsonReader} is positioned over.
@@ -76,12 +85,19 @@ public final class JsonReader {
return new JsonReader(new JsonTokener(new StringReader(s))); return new JsonReader(new JsonTokener(new StringReader(s)));
} }
/**
* Create a {@link JsonReader} from a {@link Reader}.
*/
public static JsonReader from(Reader reader) throws JsonParserException {
return new JsonReader(new JsonTokener(reader));
}
/** /**
* Internal constructor. * Internal constructor.
*/ */
JsonReader(JsonTokener tokener) throws JsonParserException { JsonReader(JsonTokener tokener) throws JsonParserException {
this.tokener = tokener; this.tokener = tokener;
token = tokener.advanceToToken(false); token = tokener.advanceToToken();
} }
/** /**
@@ -90,7 +106,8 @@ public final class JsonReader {
*/ */
public boolean pop() throws JsonParserException { public boolean pop() throws JsonParserException {
// CHECKSTYLE_OFF: EmptyStatement // CHECKSTYLE_OFF: EmptyStatement
while (!next()); while (!next())
;
// CHECKSTYLE_ON: EmptyStatement // CHECKSTYLE_ON: EmptyStatement
first = false; first = false;
inObject = states.get(--stateIndex); inObject = states.get(--stateIndex);
@@ -134,12 +151,15 @@ public final class JsonReader {
} }
/** /**
* Reads the key for the object at the current value. Does not advance to the next value. * Reads the key for the object at the current value. Does not advance to the
* next value.
*/ */
public String key() throws JsonParserException { public String key() throws JsonParserException {
if (!inObject) if (!inObject)
throw tokener.createParseException(null, "Not reading an object", true); throw tokener.createParseException(null, "Not reading an object", true);
return key.toString(); char[] chars = key.array();
chars = Arrays.copyOf(chars, key.position());
return new String(chars);
} }
/** /**
@@ -169,7 +189,8 @@ public final class JsonReader {
case JsonTokener.TOKEN_STRING: case JsonTokener.TOKEN_STRING:
return string(); return string();
default: default:
throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_TRUE, JsonTokener.TOKEN_FALSE, throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_TRUE,
JsonTokener.TOKEN_FALSE,
JsonTokener.TOKEN_NUMBER, JsonTokener.TOKEN_STRING); JsonTokener.TOKEN_NUMBER, JsonTokener.TOKEN_STRING);
} }
} }
@@ -190,7 +211,9 @@ public final class JsonReader {
return null; return null;
if (token != JsonTokener.TOKEN_STRING) if (token != JsonTokener.TOKEN_STRING)
throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_STRING); throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_STRING);
return tokener.reusableBuffer.toString(); char[] chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return new String(chars);
} }
/** /**
@@ -211,39 +234,45 @@ public final class JsonReader {
public Number number() throws JsonParserException { public Number number() throws JsonParserException {
if (token == JsonTokener.TOKEN_NULL) if (token == JsonTokener.TOKEN_NULL)
return null; return null;
return new JsonLazyNumber(tokener.reusableBuffer.toString(), tokener.isDouble); char[] chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return new JsonLazyNumber(chars, tokener.isDouble);
} }
/** /**
* Parses the current value as a long. * Parses the current value as a long.
*/ */
public long longVal() throws JsonParserException { public long longVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString(); char[] chars = tokener.reusableBuffer.array();
return tokener.isDouble ? (long)Double.parseDouble(s) : Long.parseLong(s); chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return tokener.isDouble ? (long) JavaDoubleParser.parseDouble(chars) : Long.parseLong(new String(chars));
} }
/** /**
* Parses the current value as an integer. * Parses the current value as an integer.
*/ */
public int intVal() throws JsonParserException { public int intVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString(); char[] chars = tokener.reusableBuffer.array();
return tokener.isDouble ? (int)Double.parseDouble(s) : Integer.parseInt(s); chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return tokener.isDouble ? (int) JavaDoubleParser.parseDouble(chars) : Integer.parseInt(new String(chars));
} }
/** /**
* Parses the current value as a float. * Parses the current value as a float.
*/ */
public float floatVal() throws JsonParserException { public float floatVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString(); char[] chars = tokener.reusableBuffer.array();
return Float.parseFloat(s); chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return JavaFloatParser.parseFloat(chars);
} }
/** /**
* Parses the current value as a double. * Parses the current value as a double.
*/ */
public double doubleVal() throws JsonParserException { public double doubleVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString(); char[] chars = tokener.reusableBuffer.array();
return Double.parseDouble(s); chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return JavaDoubleParser.parseDouble(chars);
} }
/** /**
@@ -259,7 +288,7 @@ public final class JsonReader {
throw tokener.createParseException(null, "Unabled to call next() at the root", true); throw tokener.createParseException(null, "Unabled to call next() at the root", true);
} }
token = tokener.advanceToToken(false); token = tokener.advanceToToken();
if (inObject) { if (inObject) {
if (token == JsonTokener.TOKEN_OBJECT_END) { if (token == JsonTokener.TOKEN_OBJECT_END) {
@@ -271,16 +300,18 @@ public final class JsonReader {
if (!first) { if (!first) {
if (token != JsonTokener.TOKEN_COMMA) if (token != JsonTokener.TOKEN_COMMA)
throw createTokenMismatchException(JsonTokener.TOKEN_COMMA, JsonTokener.TOKEN_OBJECT_END); throw createTokenMismatchException(JsonTokener.TOKEN_COMMA, JsonTokener.TOKEN_OBJECT_END);
token = tokener.advanceToToken(false); token = tokener.advanceToToken();
} }
if (token != JsonTokener.TOKEN_STRING) if (token != JsonTokener.TOKEN_STRING)
throw createTokenMismatchException(JsonTokener.TOKEN_STRING); throw createTokenMismatchException(JsonTokener.TOKEN_STRING);
key.setLength(0); key.clear();
key.append(tokener.reusableBuffer); // reduce string garbage char[] chars = tokener.reusableBuffer.array();
if ((token = tokener.advanceToToken(false)) != JsonTokener.TOKEN_COLON) chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
key.put(chars);
if ((token = tokener.advanceToToken()) != JsonTokener.TOKEN_COLON)
throw createTokenMismatchException(JsonTokener.TOKEN_COLON); throw createTokenMismatchException(JsonTokener.TOKEN_COLON);
token = tokener.advanceToToken(false); token = tokener.advanceToToken();
} else { } else {
if (token == JsonTokener.TOKEN_ARRAY_END) { if (token == JsonTokener.TOKEN_ARRAY_END) {
inObject = states.get(--stateIndex); inObject = states.get(--stateIndex);
@@ -290,7 +321,7 @@ public final class JsonReader {
if (!first) { if (!first) {
if (token != JsonTokener.TOKEN_COMMA) if (token != JsonTokener.TOKEN_COMMA)
throw createTokenMismatchException(JsonTokener.TOKEN_COMMA, JsonTokener.TOKEN_ARRAY_END); throw createTokenMismatchException(JsonTokener.TOKEN_COMMA, JsonTokener.TOKEN_ARRAY_END);
token = tokener.advanceToToken(false); token = tokener.advanceToToken();
} }
} }
@@ -307,6 +338,21 @@ public final class JsonReader {
return true; return true;
} }
/**
* Releases resources used by this JsonReader. Should be called when done
* reading.
*/
@Override
public void close() throws IOException {
if (key != null) {
CharBufferPool.release(key);
key = null;
}
if (tokener != null) {
tokener.close();
}
}
private JsonParserException createTokenMismatchException(int... t) { private JsonParserException createTokenMismatchException(int... t) {
return tokener.createParseException(null, "token mismatch (expected " + Arrays.toString(t) return tokener.createParseException(null, "token mismatch (expected " + Arrays.toString(t)
+ ", was " + token + ")", + ", was " + token + ")",

View File

@@ -159,4 +159,9 @@ public interface JsonSink<SELF extends JsonSink<SELF>> {
* Ends the current array or object. * Ends the current array or object.
*/ */
SELF end(); SELF end();
/**
* Writes the key of a key/value pair.
*/
SELF key(String key);
} }

View File

@@ -17,20 +17,25 @@ package com.grack.nanojson;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.nio.CharBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/** /**
* Internal class for tokenizing JSON. Used by both {@link JsonParser} and {@link JsonReader}. * Internal class for tokenizing JSON. Used by both {@link JsonParser} and
* {@link JsonReader}.
*/ */
final class JsonTokener { final class JsonTokener implements Closeable {
// Used by tests // Used by tests
static final int BUFFER_SIZE = 32 * 1024; static final int BUFFER_SIZE = 32 * 1024;
static final int MAX_CHAR_BUFFER_SIZE = 512;
static final int BUFFER_ROOM = 256; static final int BUFFER_ROOM = 256;
static final int MAX_ESCAPE = 5; // uXXXX (don't need the leading slash) static final int MAX_ESCAPE = 5; // uXXXX (don't need the leading slash)
@@ -38,14 +43,14 @@ final class JsonTokener {
private int tokenCharPos, tokenCharOffset; private int tokenCharPos, tokenCharOffset;
private boolean eof; private boolean eof;
protected int index; private int index;
private final Reader reader; private final Reader reader;
private final char[] buffer = new char[BUFFER_SIZE]; private final char[] buffer = new char[BUFFER_SIZE];
private int bufferLength; private int bufferLength;
private final boolean utf8; private final boolean utf8;
protected StringBuilder reusableBuffer = new StringBuilder(); protected CharBuffer reusableBuffer = CharBufferPool.get(MAX_CHAR_BUFFER_SIZE);
protected boolean isDouble; protected boolean isDouble;
static final char[] TRUE = { 'r', 'u', 'e' }; static final char[] TRUE = { 'r', 'u', 'e' };
@@ -64,11 +69,11 @@ final class JsonTokener {
static final int TOKEN_NUMBER = 9; static final int TOKEN_NUMBER = 9;
static final int TOKEN_OBJECT_START = 10; static final int TOKEN_OBJECT_START = 10;
static final int TOKEN_ARRAY_START = 11; static final int TOKEN_ARRAY_START = 11;
static final int TOKEN_SEMI_STRING = 12;
static final int TOKEN_VALUE_MIN = TOKEN_NULL; static final int TOKEN_VALUE_MIN = TOKEN_NULL;
/** /**
* A {@link Reader} that reads a UTF8 stream without decoding it for performance. * A {@link Reader} that reads a UTF8 stream without decoding it for
* performance.
*/ */
private static final class PseudoUtf8Reader extends Reader { private static final class PseudoUtf8Reader extends Reader {
private final InputStream buffered; private final InputStream buffered;
@@ -82,7 +87,7 @@ final class JsonTokener {
public int read(char[] cbuf, int off, int len) throws IOException { public int read(char[] cbuf, int off, int len) throws IOException {
int r = buffered.read(buf, off, len); int r = buffered.read(buf, off, len);
for (int i = off; i < off + r; i++) for (int i = off; i < off + r; i++)
cbuf[i] = (char)buf[i]; cbuf[i] = (char) buf[i];
return r; return r;
} }
@@ -176,30 +181,17 @@ final class JsonTokener {
fixupAfterRawBufferRead(); fixupAfterRawBufferRead();
// The token shouldn't end with something other than an ASCII letter // The token should end with something other than an ASCII letter
switch (peekChar()) { if (isAsciiLetter(peekChar()))
case ',':
case ':':
case '{':
case '}':
case '[':
case ']':
case ' ':
case '\n':
case '\r':
case '\t':
break;
default:
throw createHelpfulException(first, expected, expected.length); throw createHelpfulException(first, expected, expected.length);
} }
}
/** /**
* Steps through to the end of the current number token (a non-digit token). * Steps through to the end of the current number token (a non-digit token).
*/ */
void consumeTokenNumber(char savedChar) throws JsonParserException { void consumeTokenNumber(char savedChar) throws JsonParserException {
reusableBuffer.setLength(0); reusableBuffer.clear();
reusableBuffer.append(savedChar); reusableBuffer.put(savedChar);
isDouble = false; isDouble = false;
// The JSON spec is way stricter about number formats than // The JSON spec is way stricter about number formats than
@@ -214,8 +206,7 @@ final class JsonTokener {
state = 2; state = 2;
} }
outer: outer: while (true) {
while (true) {
int n = ensureBuffer(BUFFER_ROOM); int n = ensureBuffer(BUFFER_ROOM);
if (n == 0) if (n == 0)
break outer; break outer;
@@ -226,58 +217,67 @@ final class JsonTokener {
break outer; break outer;
int ns = -1; int ns = -1;
sw: sw: switch (state) {
switch (state) {
case 1: // start leading negative case 1: // start leading negative
if (nc == '0') { if (nc == '0') {
ns = 3; break sw; ns = 3;
break sw;
} }
if (nc > '0' && nc <= '9') { if (nc > '0' && nc <= '9') {
ns = 2; break sw; ns = 2;
break sw;
} }
break; break;
case 2: // no leading zero case 2: // no leading zero
case 3: // leading zero case 3: // leading zero
if ((nc >= '0' && nc <= '9') && state == 2) { if ((nc >= '0' && nc <= '9') && state == 2) {
ns = 2; break sw; ns = 2;
break sw;
} }
if (nc == '.') { if (nc == '.') {
isDouble = true; isDouble = true;
ns = 4; break sw; ns = 4;
break sw;
} }
if (nc == 'e' || nc == 'E') { if (nc == 'e' || nc == 'E') {
isDouble = true; isDouble = true;
ns = 6; break sw; ns = 6;
break sw;
} }
break; break;
case 4: // after period case 4: // after period
case 5: // after period, one digit read case 5: // after period, one digit read
if (nc >= '0' && nc <= '9') { if (nc >= '0' && nc <= '9') {
ns = 5; break sw; ns = 5;
break sw;
} }
if ((nc == 'e' || nc == 'E') && state == 5) { if ((nc == 'e' || nc == 'E') && state == 5) {
isDouble = true; isDouble = true;
ns = 6; break sw; ns = 6;
break sw;
} }
break; break;
case 6: // after exponent case 6: // after exponent
case 7: // after exponent and sign case 7: // after exponent and sign
if (nc == '+' || nc == '-' && state == 6) { if (nc == '+' || nc == '-' && state == 6) {
ns = 7; break sw; ns = 7;
break sw;
} }
if (nc >= '0' && nc <= '9') { if (nc >= '0' && nc <= '9') {
ns = 8; break sw; ns = 8;
break sw;
} }
break; break;
case 8: // after digits case 8: // after digits
if (nc >= '0' && nc <= '9') { if (nc >= '0' && nc <= '9') {
ns = 8; break sw; ns = 8;
break sw;
} }
break; break;
default: default:
assert false : "Impossible"; // will throw malformed number assert false : "Impossible"; // will throw malformed number
} }
reusableBuffer.append(nc); reusableBuffer.put(nc);
index++; index++;
if (ns == -1) if (ns == -1)
throw createParseException(null, "Malformed number: " + reusableBuffer, true); throw createParseException(null, "Malformed number: " + reusableBuffer, true);
@@ -296,44 +296,50 @@ final class JsonTokener {
} }
/** /**
* Steps through to the end of the current string token (the unescaped double quote). * Steps through to the end of the current string token (the unescaped double
* quote).
*/ */
void consumeTokenString(int cc) throws JsonParserException { void consumeTokenString() throws JsonParserException {
reusableBuffer.setLength(0); reusableBuffer.position(0);
// Assume no escapes or UTF-8 in the string to start (fast path) // Assume no escapes or UTF-8 in the string to start (fast path)
start: start: while (true) {
while (true) {
int n = ensureBuffer(BUFFER_ROOM); int n = ensureBuffer(BUFFER_ROOM);
if (n == 0) if (n == 0)
throw createParseException(null, "String was not terminated before end of input", true); throw createParseException(null, "String was not terminated before end of input", true);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
char c = stringChar(); char c = stringChar();
if (c == cc) { if (c == '"') {
// Use the index before we fixup // Use the index before we fixup
reusableBuffer.append(buffer, index - i - 1, i); expandBufferIfNeeded(i);
reusableBuffer.put(buffer, index - i - 1, i);
fixupAfterRawBufferRead(); fixupAfterRawBufferRead();
return; return;
} }
if (c == '\\' || (utf8 && (c & 0x80) != 0)) { if (c == '\\' || (utf8 && (c & 0x80) != 0)) {
reusableBuffer.append(buffer, index - i - 1, i); expandBufferIfNeeded(i);
reusableBuffer.put(buffer, index - i - 1, i);
index--; index--;
break start; break start;
} }
} }
reusableBuffer.append(buffer, index - n, n); expandBufferIfNeeded(n);
reusableBuffer.put(buffer, index - n, n);
} }
outer: outer: while (true) {
while (true) {
int n = ensureBuffer(BUFFER_ROOM); int n = ensureBuffer(BUFFER_ROOM);
if (n == 0) if (n == 0)
throw createParseException(null, "String was not terminated before end of input", true); throw createParseException(null, "String was not terminated before end of input", true);
int end = index + n; int end = index + n;
while (index < end) { while (index < end) {
// Ensure at least 1 char of space for upcoming output (common case). Escapes
// and
// UTF-8 multi-byte sequences will further ensure space as needed.
expandBufferIfNeeded(1);
char c = stringChar(); char c = stringChar();
if (utf8 && (c & 0x80) != 0) { if (utf8 && (c & 0x80) != 0) {
@@ -343,15 +349,9 @@ final class JsonTokener {
} }
switch (c) { switch (c) {
case '"': case '\"':
case '\'':
if (c == cc) {
fixupAfterRawBufferRead(); fixupAfterRawBufferRead();
return; return;
} else {
reusableBuffer.append(c);
break;
}
case '\\': case '\\':
// Ensure that we have at least MAX_ESCAPE here in the buffer // Ensure that we have at least MAX_ESCAPE here in the buffer
if (end - index < MAX_ESCAPE) { if (end - index < MAX_ESCAPE) {
@@ -361,31 +361,32 @@ final class JsonTokener {
// Make sure that there's enough chars for a \\uXXXX escape // Make sure that there's enough chars for a \\uXXXX escape
if (buffer[index] == 'u' && n < MAX_ESCAPE) { if (buffer[index] == 'u' && n < MAX_ESCAPE) {
index = bufferLength; // Reset index to last valid location index = bufferLength; // Reset index to last valid location
throw createParseException(null, "EOF encountered in the middle of a string escape", false); throw createParseException(null,
"EOF encountered in the middle of a string escape",
false);
} }
} }
char escape = buffer[index++]; char escape = buffer[index++];
switch (escape) { switch (escape) {
case 'b': case 'b':
reusableBuffer.append('\b'); reusableBuffer.put('\b');
break; break;
case 'f': case 'f':
reusableBuffer.append('\f'); reusableBuffer.put('\f');
break; break;
case 'n': case 'n':
reusableBuffer.append('\n'); reusableBuffer.put('\n');
break; break;
case 'r': case 'r':
reusableBuffer.append('\r'); reusableBuffer.put('\r');
break; break;
case 't': case 't':
reusableBuffer.append('\t'); reusableBuffer.put('\t');
break; break;
case '"': case '"':
case '\'':
case '/': case '/':
case '\\': case '\\':
reusableBuffer.append(escape); reusableBuffer.put(escape);
break; break;
case 'u': case 'u':
int escaped = 0; int escaped = 0;
@@ -400,155 +401,28 @@ final class JsonTokener {
} else if (digit >= 'a' && digit <= 'f') { } else if (digit >= 'a' && digit <= 'f') {
escaped |= (digit - 'a') + 10; escaped |= (digit - 'a') + 10;
} else { } else {
throw createParseException(null, "Expected unicode hex escape character: " throw createParseException(null,
+ (char)digit + " (" + digit + ")", false); "Expected unicode hex escape character: "
+ (char) digit + " (" + digit + ")", false);
} }
} }
reusableBuffer.append((char)escaped); reusableBuffer.put((char) escaped);
break; break;
default: default:
throw createParseException(null, "Invalid escape: \\" + escape, false); throw createParseException(null, "Invalid escape: \\" + escape, false);
} }
break; break;
default: default:
reusableBuffer.append(c); reusableBuffer.put(c);
} }
} }
if (index > bufferLength) { if (index > bufferLength) {
index = bufferLength; // Reset index to last valid location index = bufferLength; // Reset index to last valid location
throw createParseException(null, "EOF encountered in the middle of a string escape", false); throw createParseException(null,
} "EOF encountered in the middle of a string escape",
} false);
}
void consumeTokenSemiString() throws JsonParserException {
reusableBuffer.setLength(0);
start:
while (true) {
int n = ensureBuffer(BUFFER_ROOM);
if (n == 0)
throw createParseException(null, "String was not terminated before end of input", true);
for (int i = 0; i < n; i++) {
char c = stringChar();
if (isWhitespace(c) || c == ':') {
// Use the index before we fixup
reusableBuffer.append(buffer, index - i - 1, i);
fixupAfterRawBufferRead();
return;
}
if (c == '\\' || (utf8 && (c & 0x80) != 0)) {
reusableBuffer.append(buffer, index - i - 1, i);
index--;
break start;
}
if (c == '[' || c == ']' || c == '{' || c == '}' || c == ',') {
throw createParseException(null, "Invalid character in semi-string: " + c, false);
}
}
reusableBuffer.append(buffer, index - n, n);
}
outer:
while (true) {
int n = ensureBuffer(BUFFER_ROOM);
if (n == 0)
throw createParseException(null, "String was not terminated before end of input", true);
int end = index + n;
while (index < end) {
char c = stringChar();
if (utf8 && (c & 0x80) != 0) {
// If it's a UTF-8 codepoint, we know it won't have special meaning
consumeTokenStringUtf8Char(c);
continue outer;
}
switch (c) {
case ' ':
case '\n':
case '\r':
case '\t':
case ':':
fixupAfterRawBufferRead();
return;
case '[':
case ']':
case '{':
case '}':
case ',':
throw createParseException(null, "Invalid character in semi-string: " + c, false);
case '\\':
// Ensure that we have at least MAX_ESCAPE here in the buffer
if (end - index < MAX_ESCAPE) {
// Re-adjust the buffer end, unlikely path
n = ensureBuffer(MAX_ESCAPE);
end = index + n;
// Make sure that there's enough chars for a \\uXXXX escape
if (buffer[index] == 'u' && n < MAX_ESCAPE) {
index = bufferLength; // Reset index to last valid location
throw createParseException(null, "EOF encountered in the middle of a string escape", false);
}
}
char escape = buffer[index++];
switch (escape) {
case 'b':
reusableBuffer.append('\b');
break;
case 'f':
reusableBuffer.append('\f');
break;
case 'n':
reusableBuffer.append('\n');
break;
case 'r':
reusableBuffer.append('\r');
break;
case 't':
reusableBuffer.append('\t');
break;
case '"':
case '/':
case '\\':
reusableBuffer.append(escape);
break;
case 'u':
int escaped = 0;
for (int j = 0; j < 4; j++) {
escaped <<= 4;
int digit = buffer[index++];
if (digit >= '0' && digit <= '9') {
escaped |= (digit - '0');
} else if (digit >= 'A' && digit <= 'F') {
escaped |= (digit - 'A') + 10;
} else if (digit >= 'a' && digit <= 'f') {
escaped |= (digit - 'a') + 10;
} else {
throw createParseException(null, "Expected unicode hex escape character: "
+ (char)digit + " (" + digit + ")", false);
}
}
reusableBuffer.append((char)escaped);
break;
default:
throw createParseException(null, "Invalid escape: \\" + escape, false);
}
break;
default:
reusableBuffer.append(c);
}
}
if (index > bufferLength) {
index = bufferLength; // Reset index to last valid location
throw createParseException(null, "EOF encountered in the middle of a string escape", false);
} }
} }
} }
@@ -557,6 +431,9 @@ final class JsonTokener {
private void consumeTokenStringUtf8Char(char c) throws JsonParserException { private void consumeTokenStringUtf8Char(char c) throws JsonParserException {
ensureBuffer(5); ensureBuffer(5);
// Worst case (supplementary plane) decodes to a surrogate pair (2 chars)
expandBufferIfNeeded(2);
// Hand-UTF8-decoding // Hand-UTF8-decoding
switch (c & 0xf0) { switch (c & 0xf0) {
case 0x80: case 0x80:
@@ -572,18 +449,19 @@ final class JsonTokener {
false); false);
// fall-through // fall-through
case 0xd0: case 0xd0:
c = (char)((c & 0x1f) << 6 | (buffer[index++] & 0x3f)); c = (char) ((c & 0x1f) << 6 | (buffer[index++] & 0x3f));
reusableBuffer.append(c); reusableBuffer.put(c);
utf8adjust++; utf8adjust++;
break; break;
case 0xe0: case 0xe0:
c = (char)((c & 0x0f) << 12 | (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f)); c = (char) ((c & 0x0f) << 12 | (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f));
utf8adjust += 2; utf8adjust += 2;
// Check for illegally-encoded surrogate - http://unicode.org/faq/utf_bom.html#utf8-4 // Check for illegally-encoded surrogate -
// http://unicode.org/faq/utf_bom.html#utf8-4
if ((c >= '\ud800' && c <= '\udbff') || (c >= '\udc00' && c <= '\udfff')) if ((c >= '\ud800' && c <= '\udbff') || (c >= '\udc00' && c <= '\udfff'))
throw createParseException(null, "Illegal UTF-8 codepoint: 0x" + Integer.toHexString(c), throw createParseException(null, "Illegal UTF-8 codepoint: 0x" + Integer.toHexString(c),
false); false);
reusableBuffer.append(c); reusableBuffer.put(c);
break; break;
case 0xf0: case 0xf0:
if ((c & 0xf) >= 5) if ((c & 0xf) >= 5)
@@ -594,8 +472,8 @@ final class JsonTokener {
switch ((c & 0xc) >> 2) { switch ((c & 0xc) >> 2) {
case 0: case 0:
case 1: case 1:
reusableBuffer.appendCodePoint((c & 7) << 18 | (buffer[index++] & 0x3f) << 12 reusableBuffer.put(Character.toChars((c & 7) << 18 | (buffer[index++] & 0x3f) << 12
| (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f)); | (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f)));
utf8adjust += 3; utf8adjust += 3;
break; break;
case 2: case 2:
@@ -604,14 +482,16 @@ final class JsonTokener {
| (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f); | (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f);
throw createParseException(null, throw createParseException(null,
"Unable to represent codepoint 0x" + Integer.toHexString(codepoint) "Unable to represent codepoint 0x" + Integer.toHexString(codepoint)
+ " in a Java string", false); + " in a Java string",
false);
case 3: case 3:
codepoint = (c & 1) << 30 | (buffer[index++] & 0x3f) << 24 | (buffer[index++] & 0x3f) << 18 codepoint = (c & 1) << 30 | (buffer[index++] & 0x3f) << 24 | (buffer[index++] & 0x3f) << 18
| (buffer[index++] & 0x3f) << 12 | (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f) << 12 | (buffer[index++] & 0x3f) << 6
| (buffer[index++] & 0x3f); | (buffer[index++] & 0x3f);
throw createParseException(null, throw createParseException(null,
"Unable to represent codepoint 0x" + Integer.toHexString(codepoint) "Unable to represent codepoint 0x" + Integer.toHexString(codepoint)
+ " in a Java string", false); + " in a Java string",
false);
default: default:
assert false : "Impossible"; assert false : "Impossible";
} }
@@ -625,7 +505,8 @@ final class JsonTokener {
} }
/** /**
* Advances a character, throwing if it is illegal in the context of a JSON string. * Advances a character, throwing if it is illegal in the context of a JSON
* string.
*/ */
private char stringChar() throws JsonParserException { private char stringChar() throws JsonParserException {
char c = buffer[index++]; char c = buffer[index++];
@@ -692,7 +573,8 @@ final class JsonTokener {
} }
/** /**
* Ensures that there is enough room in the buffer to directly access the next N chars via buffer[]. * Ensures that there is enough room in the buffer to directly access the next N
* chars via buffer[].
*/ */
int ensureBuffer(int n) throws JsonParserException { int ensureBuffer(int n) throws JsonParserException {
// We're good here // We're good here
@@ -700,7 +582,8 @@ final class JsonTokener {
return n; return n;
} }
// Nope, we need to read more, but we also have to retain whatever buffer we have // Nope, we need to read more, but we also have to retain whatever buffer we
// have
if (index > 0) { if (index > 0) {
charOffset += index; charOffset += index;
bufferLength = bufferLength - index; bufferLength = bufferLength - index;
@@ -783,10 +666,11 @@ final class JsonTokener {
} }
/** /**
* Consumes a token, first eating up any whitespace ahead of it. Note that number tokens are not necessarily valid * Consumes a token, first eating up any whitespace ahead of it. Note that
* number tokens are not necessarily valid
* numbers. * numbers.
*/ */
int advanceToToken(boolean allowSemiString) throws JsonParserException { int advanceToToken() throws JsonParserException {
int c = advanceChar(); int c = advanceChar();
while (isWhitespace(c)) while (isWhitespace(c))
c = advanceChar(); c = advanceChar();
@@ -794,7 +678,6 @@ final class JsonTokener {
tokenCharPos = index + charOffset - rowPos - utf8adjust; tokenCharPos = index + charOffset - rowPos - utf8adjust;
tokenCharOffset = charOffset + index; tokenCharOffset = charOffset + index;
int oldIndex = index;
int token; int token;
switch (c) { switch (c) {
case -1: case -1:
@@ -818,44 +701,19 @@ final class JsonTokener {
token = TOKEN_OBJECT_END; token = TOKEN_OBJECT_END;
break; break;
case 't': case 't':
try {
consumeKeyword((char) c, JsonTokener.TRUE); consumeKeyword((char) c, JsonTokener.TRUE);
token = TOKEN_TRUE; token = TOKEN_TRUE;
} catch (JsonParserException e) {
if (allowSemiString) {
index = oldIndex - 1;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
} else throw e;
}
break; break;
case 'f': case 'f':
try { consumeKeyword((char) c, JsonTokener.FALSE);
consumeKeyword((char)c, JsonTokener.FALSE);
token = TOKEN_FALSE; token = TOKEN_FALSE;
} catch (JsonParserException e) {
if (allowSemiString) {
index = oldIndex - 1;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
} else throw e;
}
break; break;
case 'n': case 'n':
try { consumeKeyword((char) c, JsonTokener.NULL);
consumeKeyword((char)c, JsonTokener.NULL);
token = TOKEN_NULL; token = TOKEN_NULL;
} catch (JsonParserException e) {
if (allowSemiString) {
index = oldIndex - 1;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
} else throw e;
}
break; break;
case '"': case '\"':
case '\'': consumeTokenString();
consumeTokenString(c);
token = TOKEN_STRING; token = TOKEN_STRING;
break; break;
case '-': case '-':
@@ -869,27 +727,20 @@ final class JsonTokener {
case '7': case '7':
case '8': case '8':
case '9': case '9':
consumeTokenNumber((char)c); consumeTokenNumber((char) c);
token = TOKEN_NUMBER; token = TOKEN_NUMBER;
break; break;
case '+': case '+':
case '.': case '.':
throw createParseException(null, "Numbers may not start with '" + (char)c + "'", true); throw createParseException(null, "Numbers may not start with '" + (char) c + "'", true);
default: default:
if (allowSemiString) {
index--;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
break;
} else {
if (isAsciiLetter(c)) if (isAsciiLetter(c))
throw createHelpfulException((char)c, null, 0); throw createHelpfulException((char) c, null, 0);
throw createParseException(null, "Unexpected character: " + (char)c, true); throw createParseException(null, "Unexpected character: " + (char) c, true);
}
} }
// consumeWhitespace(); // consumeWhitespace();
return token; return token;
} }
@@ -908,6 +759,18 @@ final class JsonTokener {
eof = refillBuffer(); eof = refillBuffer();
} }
private void expandBufferIfNeeded(int size) {
if (reusableBuffer.remaining() < size) {
int oldPos = reusableBuffer.position();
int increment = Math.max(512, size - reusableBuffer.remaining());
CharBuffer newBuffer = CharBuffer.allocate(reusableBuffer.capacity() + increment);
reusableBuffer.flip(); // position -> 0, limit -> oldPos
newBuffer.put(reusableBuffer); // copy all existing data
reusableBuffer = newBuffer;
reusableBuffer.position(oldPos); // restore write position at end
}
}
/** /**
* Throws a helpful exception based on the current alphanumeric token. * Throws a helpful exception based on the current alphanumeric token.
*/ */
@@ -919,14 +782,30 @@ final class JsonTokener {
// Consume the whole pseudo-token to make a better error message // Consume the whole pseudo-token to make a better error message
while (isAsciiLetter(peekChar()) && errorToken.length() < 15) while (isAsciiLetter(peekChar()) && errorToken.length() < 15)
errorToken.append((char)advanceChar()); errorToken.append((char) advanceChar());
return createParseException(null, "Unexpected token '" + errorToken + "'" return createParseException(null, "Unexpected token '" + errorToken + "'"
+ (expected == null ? "" : ". Did you mean '" + first + new String(expected) + "'?"), true); + (expected == null ? "" : ". Did you mean '" + first + new String(expected) + "'?"), true);
} }
/** /**
* Creates a {@link JsonParserException} and fills it from the current line and char position. * Releases resources used by this JsonTokener. Should be called when done
* tokenizing.
*/
@Override
public void close() throws IOException {
if (reusableBuffer != null) {
CharBufferPool.release(reusableBuffer);
reusableBuffer = null;
}
if (reader != null) {
reader.close();
}
}
/**
* Creates a {@link JsonParserException} and fills it from the current line and
* char position.
*/ */
JsonParserException createParseException(Exception e, String message, boolean tokenPos) { JsonParserException createParseException(Exception e, String message, boolean tokenPos) {
if (tokenPos) if (tokenPos)

View File

@@ -151,7 +151,7 @@ public final class JsonWriter {
* </pre> * </pre>
*/ */
//@formatter:on //@formatter:on
public static JsonWriterContext indent(String indent) { public static JsonWriter.JsonWriterContext indent(String indent) {
if (indent == null) { if (indent == null) {
throw new IllegalArgumentException("indent must be non-null"); throw new IllegalArgumentException("indent must be non-null");
} }

View File

@@ -50,6 +50,7 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
private int stateIndex = 0; private int stateIndex = 0;
private boolean first = true; private boolean first = true;
private boolean inObject; private boolean inObject;
private String pendingKey;
/** /**
* Sequence to use for indenting. * Sequence to use for indenting.
@@ -123,7 +124,8 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
Object o = entry.getValue(); Object o = entry.getValue();
if (!(entry.getKey() instanceof String)) if (!(entry.getKey() instanceof String))
throw new JsonWriterException("Invalid key type for map: " throw new JsonWriterException("Invalid key type for map: "
+ (entry.getKey() == null ? "null" : entry.getKey() + (entry.getKey() == null ? "null"
: entry.getKey()
.getClass())); .getClass()));
String k = (String) entry.getKey(); String k = (String) entry.getKey();
value(k, o); value(k, o);
@@ -152,6 +154,8 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
return nul(); return nul();
else if (o instanceof String) else if (o instanceof String)
return value((String) o); return value((String) o);
else if (o instanceof LazyString)
return value(o.toString());
else if (o instanceof Number) else if (o instanceof Number)
return value(((Number) o)); return value(((Number) o));
else if (o instanceof Boolean) else if (o instanceof Boolean)
@@ -166,7 +170,9 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
value(Array.get(o, i)); value(Array.get(o, i));
return end(); return end();
} else } else if (o instanceof JsonConvertible)
return value(((JsonConvertible) o).toJsonValue());
else
throw new JsonWriterException("Unable to handle type: " throw new JsonWriterException("Unable to handle type: "
+ o.getClass()); + o.getClass());
} }
@@ -177,6 +183,8 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
return nul(key); return nul(key);
else if (o instanceof String) else if (o instanceof String)
return value(key, (String) o); return value(key, (String) o);
else if (o instanceof LazyString)
return value(key, o.toString());
else if (o instanceof Number) else if (o instanceof Number)
return value(key, (Number) o); return value(key, (Number) o);
else if (o instanceof Boolean) else if (o instanceof Boolean)
@@ -191,7 +199,9 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
value(Array.get(o, i)); value(Array.get(o, i));
return end(); return end();
} else } else if (o instanceof JsonConvertible)
return value(key, ((JsonConvertible) o).toJsonValue());
else
throw new JsonWriterException("Unable to handle type: " throw new JsonWriterException("Unable to handle type: "
+ o.getClass()); + o.getClass());
} }
@@ -243,7 +253,7 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
@Override @Override
public SELF value(Number n) { public SELF value(Number n) {
preValue(); preValue();
if (n == null) if (n == null || nullish(n))
raw(NULL); raw(NULL);
else else
raw(n.toString()); raw(n.toString());
@@ -372,12 +382,25 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
return castThis(); return castThis();
} }
@Override
public SELF key(String key) {
if (key == null)
throw new NullPointerException("key");
if (pendingKey != null)
throw new JsonWriterException(
"Invalid call to emit a key immediately after emitting a key");
pendingKey = key;
return castThis();
}
/** /**
* Ensures that the object is in the finished state. * Ensures that the object is in the finished state.
* *
* @throws JsonWriterException * @throws JsonWriterException
* if the written JSON is not properly balanced, ie: all arrays * if the written JSON is not properly balanced, ie:
* and objects that were started have been properly ended. * all arrays
* and objects that were started have been properly
* ended.
*/ */
protected void doneInternal() { protected void doneInternal() {
if (stateIndex > 0) if (stateIndex > 0)
@@ -434,7 +457,7 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
if (utf8) { if (utf8) {
if (bo + 1 > BUFFER_SIZE) if (bo + 1 > BUFFER_SIZE)
flush(); flush();
bb[bo++] = (byte)c; bb[bo++] = (byte) c;
} else { } else {
buffer.append(c); buffer.append(c);
if (buffer.length() > BUFFER_SIZE) { if (buffer.length() > BUFFER_SIZE) {
@@ -472,6 +495,12 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
} }
private void preValue() { private void preValue() {
if (pendingKey != null) {
String key = pendingKey;
pendingKey = null;
preValue(key);
return;
}
if (inObject) if (inObject)
throw new JsonWriterException( throw new JsonWriterException(
"Invalid call to emit a keyless value while writing an object"); "Invalid call to emit a keyless value while writing an object");
@@ -483,6 +512,9 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
if (!inObject) if (!inObject)
throw new JsonWriterException( throw new JsonWriterException(
"Invalid call to emit a key value while not writing an object"); "Invalid call to emit a key value while not writing an object");
if (pendingKey != null)
throw new JsonWriterException(
"Invalid call to emit a key value immediately after emitting a key");
pre(); pre();
@@ -547,7 +579,12 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
} }
} else { } else {
if (utf8) { if (utf8) {
if (bo + 4 > BUFFER_SIZE) // 4 is the max char size // Ensure space for the largest possible UTF-8 sequence (4 bytes) before
// encoding. Even if this char ultimately encodes to 1,2 or 3 bytes,
// reserving for 4 keeps logic simple and guarantees we never start a
// multi-byte sequence that would be split across a flush.
if (bo + 4 > BUFFER_SIZE) // 4 is the max UTF-8 byte length for a single Unicode scalar
// value
flush(); flush();
if (c < 0x80) { if (c < 0x80) {
bb[bo++] = (byte) c; bb[bo++] = (byte) c;
@@ -558,20 +595,39 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
bb[bo++] = (byte) (0xe0 | c >> 12); bb[bo++] = (byte) (0xe0 | c >> 12);
bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f); bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | c & 0x3f); bb[bo++] = (byte) (0x80 | c & 0x3f);
} else if (c < 0xdfff) { } else if (Character.isHighSurrogate(c)) {
// TODO: bad surrogates // Surrogate pair handling (supplementary plane character)
i++; // We have a high surrogate; must be followed by a low surrogate to form a valid
// code point.
int fc = Character.toCodePoint(c, s.charAt(i)); if (i + 1 >= s.length()) {
if (fc < 0x1fffff) { throw new JsonWriterException("Invalid high surrogate at end of string");
bb[bo++] = (byte) (0xf0 | fc >> 18); }
char lowSurrogate = s.charAt(i + 1);
if (!Character.isLowSurrogate(lowSurrogate)) {
throw new JsonWriterException("Invalid surrogate pair: "
+ "high surrogate not followed by low surrogate");
}
// Need 4 bytes for any supplementary code point in UTF-8. Flush first if
// insufficient space
// so the 4-byte sequence is never split across buffers.
if (bo + 4 > BUFFER_SIZE)
flush();
i++; // consume the low surrogate
int fc = Character.toCodePoint(c, lowSurrogate); // full scalar value
// Unicode scalar values are defined only up to U+10FFFF
// (exclusive upper bound 0x110000).
if (fc < 0x110000) {
bb[bo++] = (byte) (0xf0 | (fc >> 18));
bb[bo++] = (byte) (0x80 | (fc >> 12) & 0x3f); bb[bo++] = (byte) (0x80 | (fc >> 12) & 0x3f);
bb[bo++] = (byte) (0x80 | (fc >> 6) & 0x3f); bb[bo++] = (byte) (0x80 | (fc >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | fc & 0x3f); bb[bo++] = (byte) (0x80 | fc & 0x3f);
} else { } else {
throw new JsonWriterException("Unable to encode character 0x" throw new JsonWriterException(
+ Integer.toHexString(fc)); "Unable to encode character 0x" + Integer.toHexString(fc));
} }
} else if (Character.isLowSurrogate(c)) {
throw new JsonWriterException(
"Invalid low surrogate without preceding high surrogate");
} else { } else {
bb[bo++] = (byte) (0xe0 | c >> 12); bb[bo++] = (byte) (0xe0 | c >> 12);
bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f); bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f);
@@ -594,4 +650,25 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
return c < ' ' || (c >= '\u0080' && c < '\u00a0') return c < ' ' || (c >= '\u0080' && c < '\u00a0')
|| (c >= '\u2000' && c < '\u2100'); || (c >= '\u2000' && c < '\u2100');
} }
/**
* Returns true if the number becomes null when converted to JSON. json.org spec
* does not specify
* NaN or Infinity as numbers, and modern JavaScript engines convert them to
* null.
*
* @param n a number
* @return true if the number is nullish.
*/
private boolean nullish(Number n) {
if (n instanceof Double) {
Double d = (Double) n;
return d.isNaN() || d.isInfinite();
}
if (n instanceof Float) {
Float f = (Float) n;
return f.isNaN() || f.isInfinite();
}
return false;
}
} }

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2011 The nanojson Authors
*
* 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 com.grack.nanojson;
import java.util.Arrays;
public class LazyString implements CharSequence {
private final int length;
private char[] value;
private String stringValue;
private final Object lock = new Object();
public LazyString(char[] value) {
this.value = value;
this.length = value.length;
}
public LazyString(String value) {
this.stringValue = value;
this.length = value.length();
}
@Override
public int length() {
return length;
}
@Override
public char charAt(int index) {
String str = stringValue; // Local ref copy to avoid race
if (str != null) {
return str.charAt(index);
}
char[] arr = value; // Local ref copy to avoid race
if (arr != null) {
return arr[index];
}
// Fallback if value was nullified
return toString().charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
String str = stringValue; // Local ref copy to avoid race
if (str != null) {
return str.subSequence(start, end);
}
char[] arr = value; // Local ref copy to avoid race
if (arr != null) {
return new LazyString(Arrays.copyOfRange(arr, start, end));
}
// Fallback to string-based subsequence
return toString().subSequence(start, end);
}
public String toString() {
if (stringValue != null) {
return stringValue;
}
synchronized (lock) {
if (stringValue == null) {
stringValue = new String(value);
}
value = null; // Clear the char array to save memory
}
return stringValue;
}
@Override
public int hashCode() {
String str = stringValue; // Local ref copy to avoid race
if (str != null) {
return str.hashCode();
}
char[] arr = value; // Local ref copy to avoid race
if (arr != null) {
return Arrays.hashCode(arr);
}
return toString().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof CharSequence))
return false;
if (obj instanceof LazyString) {
LazyString other = (LazyString) obj;
String str = stringValue; // Local ref copy to avoid race
String otherStr = other.stringValue; // Local ref copy to avoid race
if (str != null && otherStr != null) {
return str.equals(otherStr);
} else if (str != null) {
return str.contentEquals((CharSequence) other);
} else if (otherStr != null) {
return otherStr.contentEquals((CharSequence) this);
}
// Both are LazyString without stringValue
char[] arr = value; // Local ref copy to avoid race
char[] otherArr = other.value; // Local ref copy to avoid race
if (arr != null && otherArr != null) {
return Arrays.equals(arr, otherArr);
}
// Fallback to string comparison
return toString().equals(other.toString());
}
return toString().contentEquals((CharSequence) obj);
}
}

View File

@@ -0,0 +1,29 @@
package com.grack.nanojson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class JsonBuilderTest {
@Test
void failureKeyInArray() {
assertThrows(JsonWriterException.class, () ->
new JsonBuilder<>(new JsonArray()).key("a"));
}
@Test
void failureKeyWhileKeyPending() {
assertThrows(JsonWriterException.class, () ->
new JsonBuilder<>(new JsonObject()).key("a").key("b"));
}
@Test
void separateKeyWriting() {
JsonObject actual = new JsonBuilder<>(new JsonObject()).key("a").value(1).key("b").value(2).done();
JsonObject expected = new JsonObject();
expected.put("a", 1);
expected.put("b", 2);
assertEquals(expected, actual);
}
}

View File

@@ -15,76 +15,71 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import static org.junit.Assert.assertEquals; import org.junit.jupiter.api.Test;
import java.math.BigInteger; import java.math.BigInteger;
import org.junit.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
/** /**
* Attempts to test that numbers are correctly round-tripped. * Attempts to test that numbers are correctly round-tripped.
*/ */
public class JsonNumberTest { class JsonNumberTest {
// CHECKSTYLE_OFF: MagicNumber // CHECKSTYLE_OFF: MagicNumber
// CHECKSTYLE_OFF: JavadocMethod // CHECKSTYLE_OFF: JavadocMethod
@Test @Test
public void testBasicNumberRead() throws JsonParserException { void basicNumberRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[1, 1.0, 1.00]"); JsonArray array = JsonParser.array().from("[1, 1.0, 1.00]");
assertEquals(Integer.class, array.get(0).getClass()); assertEquals(1, ((Number) array.get(0)).intValue());
assertEquals(Double.class, array.get(1).getClass()); assertEquals(1.0, ((Number) array.get(1)).doubleValue(), 0.0);
assertEquals(Double.class, array.get(2).getClass()); assertEquals(1.0, ((Number) array.get(2)).doubleValue(), 0.0);
} }
@Test @Test
public void testBasicNumberWrite() { void basicNumberWrite() {
JsonArray array = JsonArray.from(1, 1.0, 1.0f); JsonArray array = JsonArray.from(1, 1.0, 1.0f);
assertEquals("[1,1.0,1.0]", JsonWriter.string().array(array).done()); assertEquals("[1,1.0,1.0]", JsonWriter.string().array(array).done());
} }
@Test @Test
public void testLargeIntRead() throws JsonParserException { void largeIntRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[-300000000,300000000]"); JsonArray array = JsonParser.array().from("[-300000000,300000000]");
assertEquals(Integer.class, array.get(0).getClass()); assertEquals(-300000000, ((Number) array.get(0)).intValue());
assertEquals(-300000000, array.get(0)); assertEquals(300000000, ((Number) array.get(1)).intValue());
assertEquals(Integer.class, array.get(1).getClass());
assertEquals(300000000, array.get(1));
} }
@Test @Test
public void testLargeIntWrite() { void largeIntWrite() {
JsonArray array = JsonArray.from(-300000000, 300000000); JsonArray array = JsonArray.from(-300000000, 300000000);
assertEquals("[-300000000,300000000]", JsonWriter.string().array(array) assertEquals("[-300000000,300000000]", JsonWriter.string().array(array)
.done()); .done());
} }
@Test @Test
public void testLongRead() throws JsonParserException { void longRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[-3000000000,3000000000]"); JsonArray array = JsonParser.array().from("[-3000000000,3000000000]");
assertEquals(Long.class, array.get(0).getClass()); assertEquals(-3000000000L, ((Number) array.get(0)).longValue());
assertEquals(-3000000000L, array.get(0)); assertEquals(3000000000L, ((Number) array.get(1)).longValue());
assertEquals(Long.class, array.get(1).getClass());
assertEquals(3000000000L, array.get(1));
} }
@Test @Test
public void testLongWrite() { void longWrite() {
JsonArray array = JsonArray.from(1L, -3000000000L, 3000000000L); JsonArray array = JsonArray.from(1L, -3000000000L, 3000000000L);
assertEquals("[1,-3000000000,3000000000]", assertEquals("[1,-3000000000,3000000000]",
JsonWriter.string().array(array).done()); JsonWriter.string().array(array).done());
} }
@Test @Test
public void testBigIntRead() throws JsonParserException { void bigIntRead() throws JsonParserException {
JsonArray array = JsonParser.array().from( JsonArray array = JsonParser.array().from(
"[-30000000000000000000,30000000000000000000]"); "[-30000000000000000000,30000000000000000000]");
assertEquals(BigInteger.class, array.get(0).getClass()); // cast to ensure it's a number
assertEquals(new BigInteger("-30000000000000000000"), array.get(0)); assertEquals("-30000000000000000000", ((Number) array.get(0)).toString());
assertEquals(BigInteger.class, array.get(1).getClass()); assertEquals("30000000000000000000", ((Number) array.get(1)).toString());
assertEquals(new BigInteger("30000000000000000000"), array.get(1));
} }
@Test @Test
public void testBigIntWrite() { void bigIntWrite() {
JsonArray array = JsonArray.from(BigInteger.ONE, new BigInteger( JsonArray array = JsonArray.from(BigInteger.ONE, new BigInteger(
"-30000000000000000000"), "-30000000000000000000"),
new BigInteger("30000000000000000000")); new BigInteger("30000000000000000000"));
@@ -96,7 +91,7 @@ public class JsonNumberTest {
* Tests a bug where longs were silently truncated to floats. * Tests a bug where longs were silently truncated to floats.
*/ */
@Test @Test
public void testLongBuilder() { void longBuilder() {
JsonObject o = JsonObject.builder().value("long", 0xffffffffffffL) JsonObject o = JsonObject.builder().value("long", 0xffffffffffffL)
.done(); .done();
assertEquals(0xffffffffffffL, o.getNumber("long").longValue()); assertEquals(0xffffffffffffL, o.getNumber("long").longValue());
@@ -106,7 +101,7 @@ public class JsonNumberTest {
* Test around the edges of the integral types. * Test around the edges of the integral types.
*/ */
@Test @Test
public void testAroundEdges() throws JsonParserException { void aroundEdges() throws JsonParserException {
JsonArray array = JsonArray.from(Integer.MAX_VALUE, JsonArray array = JsonArray.from(Integer.MAX_VALUE,
((long) Integer.MAX_VALUE) + 1, Integer.MIN_VALUE, ((long) Integer.MAX_VALUE) + 1, Integer.MIN_VALUE,
((long) Integer.MIN_VALUE) - 1, Long.MAX_VALUE, BigInteger ((long) Integer.MIN_VALUE) - 1, Long.MAX_VALUE, BigInteger
@@ -122,4 +117,11 @@ public class JsonNumberTest {
String json2 = JsonWriter.string().array(array2).done(); String json2 = JsonWriter.string().array(array2).done();
assertEquals(json, json2); assertEquals(json, json2);
} }
@Test
void trailingDecimalLazy() throws JsonParserException {
Object value = JsonParser.any().withLazyNumbers().from("1.000");
String json = JsonWriter.string().value(value).done();
assertEquals("1.000", json);
}
} }

View File

@@ -15,29 +15,28 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.fail; import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import org.junit.Test; import org.junit.jupiter.api.Test;
/** /**
* Test for {@link JsonParser}. * Test for {@link JsonParser}.
*/ */
public class JsonParserTest { class JsonParserTest {
private static final Charset UTF8; private static final Charset UTF8;
static { static {
@@ -48,7 +47,7 @@ public class JsonParserTest {
// CHECKSTYLE_OFF: JavadocMethod // CHECKSTYLE_OFF: JavadocMethod
// CHECKSTYLE_OFF: EmptyBlock // CHECKSTYLE_OFF: EmptyBlock
@Test @Test
public void testWhitespace() throws JsonParserException { void whitespace() throws JsonParserException {
assertEquals(JsonObject.class, assertEquals(JsonObject.class,
JsonParser.object().from(" \t\r\n { \t\r\n \"abc\" \t\r\n : \t\r\n 1 \t\r\n } \t\r\n ") JsonParser.object().from(" \t\r\n { \t\r\n \"abc\" \t\r\n : \t\r\n 1 \t\r\n } \t\r\n ")
.getClass()); .getClass());
@@ -56,15 +55,14 @@ public class JsonParserTest {
} }
@Test @Test
public void testWhitespaceSimpler() throws JsonParserException { void whitespaceSimpler() throws JsonParserException {
assertEquals(JsonObject.class, assertEquals(JsonObject.class,
JsonParser.object().from(" {} ") JsonParser.object().from(" {} ")
.getClass()); .getClass());
} }
@Test @Test
public void testWriterOutput() throws JsonParserException { void writerOutput() throws JsonParserException {
//@formatter:off //@formatter:off
String json = JsonWriter.string() String json = JsonWriter.string()
.object() .object()
@@ -84,52 +82,50 @@ public class JsonParserTest {
.end() .end()
.done(); .done();
//@formatter:on //@formatter:on
JsonParser.object().from(json); // ensure parseable
// Just make sure it can be read - don't validate
JsonParser.object().from(json);
} }
@Test @Test
public void testEmptyObject() throws JsonParserException { void emptyObject() throws JsonParserException {
assertEquals(JsonObject.class, JsonParser.object().from("{}").getClass()); assertEquals(JsonObject.class, JsonParser.object().from("{}").getClass());
assertEquals("{}", JsonParser.object().from("{}").toString()); assertEquals("{}", JsonParser.object().from("{}").toString());
} }
@Test @Test
public void testObjectOneElement() throws JsonParserException { void objectOneElement() throws JsonParserException {
assertEquals(JsonObject.class, JsonParser.object().from("{\"a\":1}").getClass()); assertEquals(JsonObject.class, JsonParser.object().from("{\"a\":1}").getClass());
assertEquals("{a=1}", JsonParser.object().from("{\"a\":1}").toString()); assertEquals("{a=1}", JsonParser.object().from("{\"a\":1}").toString());
} }
@Test @Test
public void testObjectTwoElements() throws JsonParserException { void objectTwoElements() throws JsonParserException {
JsonObject obj = JsonParser.object().from("{\"a\":1,\"B\":1}"); JsonObject obj = JsonParser.object().from("{\"a\":1,\"B\":1}");
assertEquals(JsonObject.class, obj.getClass()); assertEquals(JsonObject.class, obj.getClass());
assertEquals(1, obj.get("B")); assertEquals(1, obj.getInt("B"));
assertEquals(1, obj.get("a")); assertEquals(1, obj.getInt("a"));
assertEquals(2, obj.size()); assertEquals(2, obj.size());
} }
@Test @Test
public void testEmptyArray() throws JsonParserException { void emptyArray() throws JsonParserException {
assertEquals(JsonArray.class, JsonParser.array().from("[]").getClass()); assertEquals(JsonArray.class, JsonParser.array().from("[]").getClass());
assertEquals("[]", JsonParser.array().from("[]").toString()); assertEquals("[]", JsonParser.array().from("[]").toString());
} }
@Test @Test
public void testArrayOneElement() throws JsonParserException { void arrayOneElement() throws JsonParserException {
assertEquals(JsonArray.class, JsonParser.array().from("[1]").getClass()); assertEquals(JsonArray.class, JsonParser.array().from("[1]").getClass());
assertEquals("[1]", JsonParser.array().from("[1]").toString()); assertEquals("[1]", JsonParser.array().from("[1]").toString());
} }
@Test @Test
public void testArrayTwoElements() throws JsonParserException { void arrayTwoElements() throws JsonParserException {
assertEquals(JsonArray.class, JsonParser.array().from("[1,1]").getClass()); assertEquals(JsonArray.class, JsonParser.array().from("[1,1]").getClass());
assertEquals("[1, 1]", JsonParser.array().from("[1,1]").toString()); assertEquals("[1, 1]", JsonParser.array().from("[1,1]").toString());
} }
@Test @Test
public void testBasicTypes() throws JsonParserException { void basicTypes() throws JsonParserException {
assertEquals("true", JsonParser.any().from("true").toString()); assertEquals("true", JsonParser.any().from("true").toString());
assertEquals("false", JsonParser.any().from("false").toString()); assertEquals("false", JsonParser.any().from("false").toString());
assertNull(JsonParser.any().from("null")); assertNull(JsonParser.any().from("null"));
@@ -140,9 +136,9 @@ public class JsonParserTest {
} }
@Test @Test
public void testArrayWithEverything() throws JsonParserException { void arrayWithEverything() throws JsonParserException {
JsonArray a = JsonParser.array().from("[1, -1.0e6, \"abc\", [1,2,3], {\"abc\":123}, true, false]"); JsonArray a = JsonParser.array().from("[1, -1.0e6, \"abc\", [1,2,3], {\"abc\":123}, true, false]");
assertEquals("[1, -1000000.0, abc, [1, 2, 3], {abc=123}, true, false]", a.toString()); assertEquals("[1, -1.0e6, abc, [1, 2, 3], {abc=123}, true, false]", a.toString());
assertEquals(1.0, a.getDouble(0), 0.001f); assertEquals(1.0, a.getDouble(0), 0.001f);
assertEquals(1, a.getInt(0)); assertEquals(1, a.getInt(0));
assertEquals(-1000000, a.getInt(1)); assertEquals(-1000000, a.getInt(1));
@@ -155,94 +151,95 @@ public class JsonParserTest {
} }
@Test @Test
public void testObjectWithEverything() throws JsonParserException { void objectWithEverything() throws JsonParserException {
// TODO: Is this deterministic if we use string keys? // TODO: Is this deterministic if we use string keys?
JsonObject o = JsonParser.object().from( JsonObject o = JsonParser.object().from(
"{\"abc\":123, \"def\":456.0, \"ghi\":[true, false], \"jkl\":null, \"mno\":true}"); "{\"abc\":123, \"def\":456.0, \"ghi\":[true, false], \"jkl\":null, \"mno\":true}");
assertEquals(null, o.get("jkl")); assertNull(o.get("jkl"));
assertTrue(o.containsKey("jkl")); assertTrue(o.containsKey("jkl"));
assertEquals(123, o.get("abc")); assertEquals(123, ((Number) o.get("abc")).intValue());
assertEquals(Arrays.asList(true, false), o.get("ghi")); assertEquals(Arrays.asList(true, false), o.get("ghi"));
assertEquals(456.0, o.get("def")); assertEquals(456.0, ((Number) o.get("def")).doubleValue());
assertEquals(true, o.get("mno")); assertEquals(true, o.get("mno"));
assertEquals(5, o.size()); assertEquals(5, o.size());
assertEquals(123, o.getInt("abc")); assertEquals(123, o.getInt("abc"));
assertEquals(456, o.getInt("def")); assertEquals(456, o.getInt("def"));
assertEquals(true, o.getArray("ghi").getBoolean(0)); assertTrue(o.getArray("ghi").getBoolean(0));
assertEquals(null, o.get("jkl")); assertNull(o.get("jkl"));
assertTrue(o.isNull("jkl")); assertTrue(o.isNull("jkl"));
assertTrue(o.getBoolean("mno")); assertTrue(o.getBoolean("mno"));
} }
@Test @Test
public void testStringEscapes() throws JsonParserException { void stringEscapes() throws JsonParserException {
assertEquals("\n", JsonParser.any().from("\"\\n\"")); assertEquals("\n", JsonParser.any().from("\"\\n\"").toString());
assertEquals("\r", JsonParser.any().from("\"\\r\"")); assertEquals("\r", JsonParser.any().from("\"\\r\"").toString());
assertEquals("\t", JsonParser.any().from("\"\\t\"")); assertEquals("\t", JsonParser.any().from("\"\\t\"").toString());
assertEquals("\b", JsonParser.any().from("\"\\b\"")); assertEquals("\b", JsonParser.any().from("\"\\b\"").toString());
assertEquals("\f", JsonParser.any().from("\"\\f\"")); assertEquals("\f", JsonParser.any().from("\"\\f\"").toString());
assertEquals("/", JsonParser.any().from("\"/\"")); assertEquals("/", JsonParser.any().from("\"/\"").toString());
assertEquals("\\", JsonParser.any().from("\"\\\\\"")); assertEquals("\\", JsonParser.any().from("\"\\\\\"").toString());
assertEquals("\"", JsonParser.any().from("\"\\\"\"")); assertEquals("\"", JsonParser.any().from("\"\\\"\"").toString());
assertEquals("\0", JsonParser.any().from("\"\\u0000\"")); assertEquals("\0", JsonParser.any().from("\"\\u0000\"").toString());
assertEquals("\u8000", JsonParser.any().from("\"\\u8000\"")); assertEquals("\u8000", JsonParser.any().from("\"\\u8000\"").toString());
assertEquals("\uffff", JsonParser.any().from("\"\\uffff\"")); assertEquals("\uffff", JsonParser.any().from("\"\\uffff\"").toString());
assertEquals("\uFFFF", JsonParser.any().from("\"\\uFFFF\"")); assertEquals("\uFFFF", JsonParser.any().from("\"\\uFFFF\"").toString());
assertEquals("all together: \\/\n\r\t\b\f (fin)", assertEquals("all together: \\/\n\r\t\b\f (fin)",
JsonParser.any().from("\"all together: \\\\\\/\\n\\r\\t\\b\\f (fin)\"")); JsonParser.any().from("\"all together: \\\\\\/\\n\\r\\t\\b\\f (fin)\"").toString());
} }
@Test @Test
public void testStringEscapesAroundBufferBoundary() throws JsonParserException { void stringEscapesAroundBufferBoundary() throws JsonParserException {
char[] c = new char[JsonTokener.BUFFER_SIZE - 1024]; char[] c = new char[JsonTokener.BUFFER_SIZE - 1024];
Arrays.fill(c, ' '); Arrays.fill(c, ' ');
String base = new String(c); String base = new String(c);
for (int i = 0; i < 2048; i++) { for (int i = 0; i < 2048; i++) {
base += " "; base += " ";
assertEquals("\u0055", JsonParser.any().from(base + "\"\\u0055\"")); assertEquals("\u0055", JsonParser.any().from(base + "\"\\u0055\"").toString());
} }
} }
@Test @Test
public void testStringsAroundBufferBoundary() throws JsonParserException { void stringsAroundBufferBoundary() throws JsonParserException {
char[] c = new char[JsonTokener.BUFFER_SIZE - 16]; char[] c = new char[JsonTokener.BUFFER_SIZE - 16];
Arrays.fill(c, ' '); Arrays.fill(c, ' ');
String base = new String(c); String base = new String(c);
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
base += " "; base += " ";
assertEquals(base, JsonParser.any().from('"' + base + '"')); assertEquals(base, JsonParser.any().from('"' + base + '"').toString());
} }
} }
@Test @Test
public void testNumbers() throws JsonParserException { void numbers() throws JsonParserException {
String[] testCases = new String[] { "0", "1", "-0", "-1", "0.1", "1.1", "-0.1", "0.10", "-0.10", "0e1", "0e0", String[] testCases = new String[] { "0", "1", "-0", "-1", "0.1", "1.1", "-0.1", "0.10", "-0.10", "0e1", "0e0",
"-0e-1", "0.0e0", "-0.0e0", "9" }; "-0e-1", "0.0e0", "-0.0e0", "9" };
for (String testCase : testCases) { for (String testCase : testCases) {
Number n = (Number)JsonParser.any().from(testCase); Number n = (Number) JsonParser.any().from(testCase);
assertEquals(Double.parseDouble(testCase), n.doubleValue(), Double.MIN_NORMAL); assertEquals(Double.parseDouble(testCase), n.doubleValue(), Double.MIN_NORMAL);
Number n2 = (Number)JsonParser.any().from(testCase.toUpperCase()); Number n2 = (Number) JsonParser.any().from(testCase.toUpperCase());
assertEquals(Double.parseDouble(testCase.toUpperCase()), n2.doubleValue(), Double.MIN_NORMAL); assertEquals(Double.parseDouble(testCase.toUpperCase()), n2.doubleValue(), Double.MIN_NORMAL);
} }
} }
/** /**
* Test that negative zero ends up as negative zero in both the parser and the writer. * Test that negative zero ends up as negative zero in both the parser and the
* writer.
*/ */
@Test @Test
public void testNegativeZero() throws JsonParserException { void negativeZero() throws JsonParserException {
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0.0")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0.0")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0.0e0")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0.0e0")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0e0")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0e0")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0e1")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0e1")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0e-1")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0e-1")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0e-0")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0e-0")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0e-01")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0e-01")).doubleValue()));
assertEquals("-0.0", Double.toString(((Number)JsonParser.any().from("-0e-000000000001")).doubleValue())); assertEquals("-0.0", Double.toString(((Number) JsonParser.any().from("-0e-000000000001")).doubleValue()));
assertEquals("-0.0", JsonWriter.string(-0.0)); assertEquals("-0.0", JsonWriter.string(-0.0));
assertEquals("-0.0", JsonWriter.string(-0.0f)); assertEquals("-0.0", JsonWriter.string(-0.0f));
@@ -252,21 +249,24 @@ public class JsonParserTest {
* Test the basic numbers from -100 to 100 as a sanity check. * Test the basic numbers from -100 to 100 as a sanity check.
*/ */
@Test @Test
public void testBasicNumbers() throws JsonParserException { void basicNumbers() throws JsonParserException {
for (int i = -100; i <= +100; i++) { for (int i = -100; i <= +100; i++) {
assertEquals(i, (int)(Integer)JsonParser.any().from("" + i)); Number n = (Number) JsonParser.any().from(Integer.toString(i));
assertEquals(i, n.intValue());
} }
} }
@Test @Test
public void testBigint() throws JsonParserException { void bigint() throws JsonParserException {
JsonObject o = JsonParser.object().from("{\"v\":123456789123456789123456789}"); JsonObject o = JsonParser.object().from("{\"v\":123456789123456789123456789}");
BigInteger bigint = (BigInteger)o.get("v"); Object raw = o.get("v");
assertEquals("123456789123456789123456789", bigint.toString()); // May be parsed as JsonLazyNumber or BigInteger depending on laziness settings
String s = raw.toString();
assertEquals("123456789123456789123456789", s);
} }
@Test @Test
public void testFailWrongType() { void failWrongType() {
try { try {
JsonParser.object().from("1"); JsonParser.object().from("1");
fail("Should have failed to parse"); fail("Should have failed to parse");
@@ -276,7 +276,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailNull() { void failNull() {
try { try {
JsonParser.object().from("null"); JsonParser.object().from("null");
fail("Should have failed to parse"); fail("Should have failed to parse");
@@ -286,7 +286,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailNoJson1() { void failNoJson1() {
try { try {
JsonParser.object().from(""); JsonParser.object().from("");
fail("Should have failed to parse"); fail("Should have failed to parse");
@@ -296,7 +296,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailNoJson2() { void failNoJson2() {
try { try {
JsonParser.object().from(" "); JsonParser.object().from(" ");
fail("Should have failed to parse"); fail("Should have failed to parse");
@@ -306,7 +306,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailNoJson3() { void failNoJson3() {
try { try {
JsonParser.object().from(" "); JsonParser.object().from(" ");
fail("Should have failed to parse"); fail("Should have failed to parse");
@@ -316,7 +316,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailNumberEdgeCases() { void failNumberEdgeCases() {
String[] edgeCases = { "-", ".", "e", "01", "-01", "+01", "01.1", "-01.1", "+01.1", ".1", "-.1", "+.1", "+1", String[] edgeCases = { "-", ".", "e", "01", "-01", "+01", "01.1", "-01.1", "+01.1", ".1", "-.1", "+.1", "+1",
"0.", "-0.", "+0.", "0.e", "-0.e", "+0.e", "0e", "-0e", "+0e", "0e-", "-0e-", "+0e-", "0e+", "-0e+", "0.", "-0.", "+0.", "0.e", "-0.e", "+0.e", "0e", "-0e", "+0e", "0e-", "-0e-", "+0e-", "0e+", "-0e+",
"+0e+", "-e", "+e", "2.", "-2.", "-1.e1", "1.e1", "0.e1" }; "+0e+", "-e", "+e", "2.", "-2.", "-1.e1", "1.e1", "0.e1" };
@@ -347,10 +347,11 @@ public class JsonParserTest {
} }
/** /**
* See http://seriot.ch/json/parsing.html and https://github.com/mmastrac/nanojson/issues/3. * See http://seriot.ch/json/parsing.html and
* https://github.com/mmastrac/nanojson/issues/3.
*/ */
@Test @Test
public void testFailNumberEdgeCasesFromJSONSuite() { void failNumberEdgeCasesFromJSONSuite() {
String[] edgeCases = { "[-2.]", "[0.e1]", "[2.e+3]", "[2.e-3]", "[2.e3]", "[1.]" }; String[] edgeCases = { "[-2.]", "[0.e1]", "[2.e+3]", "[2.e-3]", "[2.e3]", "[1.]" };
for (String edgeCase : edgeCases) { for (String edgeCase : edgeCases) {
try { try {
@@ -363,10 +364,11 @@ public class JsonParserTest {
} }
/** /**
* See http://seriot.ch/json/parsing.html and https://github.com/mmastrac/nanojson/issues/3. * See http://seriot.ch/json/parsing.html and
* https://github.com/mmastrac/nanojson/issues/3.
*/ */
@Test @Test
public void testFailNumberEdgeCasesFromJSONSuiteNoArray() { void failNumberEdgeCasesFromJSONSuiteNoArray() {
String[] edgeCases = { "-2.", "0.e1", "2.e+3", "2.e-3", "2.e3", "1." }; String[] edgeCases = { "-2.", "0.e1", "2.e+3", "2.e-3", "2.e3", "1." };
for (String edgeCase : edgeCases) { for (String edgeCase : edgeCases) {
try { try {
@@ -379,7 +381,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedNumber1() { void failBustedNumber1() {
try { try {
// There's no 'f' in double, but it treats it as a new token // There's no 'f' in double, but it treats it as a new token
JsonParser.object().from("123f"); JsonParser.object().from("123f");
@@ -390,7 +392,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedNumber2() { void failBustedNumber2() {
try { try {
// Badly formed number // Badly formed number
JsonParser.object().from("-1-1"); JsonParser.object().from("-1-1");
@@ -401,7 +403,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedString1() { void failBustedString1() {
try { try {
// Missing " at end // Missing " at end
JsonParser.object().from("\"abc"); JsonParser.object().from("\"abc");
@@ -412,7 +414,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedString2() { void failBustedString2() {
try { try {
// \n in middle of string // \n in middle of string
JsonParser.object().from("\"abc\n\""); JsonParser.object().from("\"abc\n\"");
@@ -423,7 +425,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedString3() { void failBustedString3() {
try { try {
// Bad escape "\x" in middle of string // Bad escape "\x" in middle of string
JsonParser.object().from("\"abc\\x\""); JsonParser.object().from("\"abc\\x\"");
@@ -434,7 +436,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedString4() { void failBustedString4() {
try { try {
// Bad escape "\\u123x" in middle of string // Bad escape "\\u123x" in middle of string
JsonParser.object().from("\"\\u123x\""); JsonParser.object().from("\"\\u123x\"");
@@ -445,7 +447,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedString5() { void failBustedString5() {
try { try {
// Incomplete unicode escape // Incomplete unicode escape
JsonParser.object().from("\"\\u222\""); JsonParser.object().from("\"\\u222\"");
@@ -456,7 +458,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedString6() { void failBustedString6() {
try { try {
// String that terminates halfway through a unicode escape // String that terminates halfway through a unicode escape
JsonParser.object().from("\"\\u222"); JsonParser.object().from("\"\\u222");
@@ -467,7 +469,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBustedString7() { void failBustedString7() {
try { try {
// String that terminates halfway through a regular escape // String that terminates halfway through a regular escape
JsonParser.object().from("\"\\"); JsonParser.object().from("\"\\");
@@ -478,7 +480,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailArrayTrailingComma1() { void failArrayTrailingComma1() {
try { try {
JsonParser.object().from("[,]"); JsonParser.object().from("[,]");
fail(); fail();
@@ -488,7 +490,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailArrayTrailingComma2() { void failArrayTrailingComma2() {
try { try {
JsonParser.object().from("[1,]"); JsonParser.object().from("[1,]");
fail(); fail();
@@ -498,7 +500,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailObjectTrailingComma1() { void failObjectTrailingComma1() {
try { try {
JsonParser.object().from("{,}"); JsonParser.object().from("{,}");
fail(); fail();
@@ -508,7 +510,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailObjectTrailingComma2() { void failObjectTrailingComma2() {
try { try {
JsonParser.object().from("{\"abc\":123,}"); JsonParser.object().from("{\"abc\":123,}");
fail(); fail();
@@ -518,7 +520,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailObjectBadKey1() { void failObjectBadKey1() {
try { try {
JsonParser.object().from("{true:1}"); JsonParser.object().from("{true:1}");
fail(); fail();
@@ -528,7 +530,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailObjectBadKey2() { void failObjectBadKey2() {
try { try {
JsonParser.object().from("{2:1}"); JsonParser.object().from("{2:1}");
fail(); fail();
@@ -538,7 +540,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailObjectBadColon1() { void failObjectBadColon1() {
try { try {
JsonParser.object().from("{\"abc\":}"); JsonParser.object().from("{\"abc\":}");
fail(); fail();
@@ -548,7 +550,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailObjectBadColon2() { void failObjectBadColon2() {
try { try {
JsonParser.object().from("{\"abc\":1:}"); JsonParser.object().from("{\"abc\":1:}");
fail(); fail();
@@ -558,7 +560,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailObjectBadColon3() { void failObjectBadColon3() {
try { try {
JsonParser.object().from("{:\"abc\":1}"); JsonParser.object().from("{:\"abc\":1}");
fail(); fail();
@@ -568,7 +570,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBadKeywords1() { void failBadKeywords1() {
try { try {
JsonParser.object().from("truef"); JsonParser.object().from("truef");
fail(); fail();
@@ -578,7 +580,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBadKeywords2() { void failBadKeywords2() {
try { try {
JsonParser.object().from("true1"); JsonParser.object().from("true1");
fail(); fail();
@@ -588,7 +590,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBadKeywords3() { void failBadKeywords3() {
try { try {
JsonParser.object().from("tru"); JsonParser.object().from("tru");
fail(); fail();
@@ -598,7 +600,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBadKeywords4() { void failBadKeywords4() {
try { try {
JsonParser.object().from("[truef,true]"); JsonParser.object().from("[truef,true]");
fail(); fail();
@@ -608,7 +610,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBadKeywords5() { void failBadKeywords5() {
try { try {
JsonParser.object().from("grue"); JsonParser.object().from("grue");
fail(); fail();
@@ -618,7 +620,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBadKeywords6() { void failBadKeywords6() {
try { try {
JsonParser.object().from("trueeeeeeeeeeeeeeeeeeee"); JsonParser.object().from("trueeeeeeeeeeeeeeeeeeee");
fail(); fail();
@@ -628,7 +630,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailBadKeywords7() { void failBadKeywords7() {
try { try {
JsonParser.object().from("g"); JsonParser.object().from("g");
fail(); fail();
@@ -638,7 +640,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailTrailingCommaMultiline() { void failTrailingCommaMultiline() {
String testString = "{\n\"abc\":123,\n\"def\":456,\n}"; String testString = "{\n\"abc\":123,\n\"def\":456,\n}";
try { try {
JsonParser.object().from(testString); JsonParser.object().from(testString);
@@ -652,7 +654,7 @@ public class JsonParserTest {
* Ensures that we're correctly tracking UTF-8 character positions. * Ensures that we're correctly tracking UTF-8 character positions.
*/ */
@Test @Test
public void testFailTrailingCommaUTF8() { void failTrailingCommaUTF8() {
ByteArrayInputStream in1 = new ByteArrayInputStream("{\n\"abc\":123,\"def\":456,}".getBytes(Charset ByteArrayInputStream in1 = new ByteArrayInputStream("{\n\"abc\":123,\"def\":456,}".getBytes(Charset
.forName("UTF-8"))); .forName("UTF-8")));
ByteArrayInputStream in2 = new ByteArrayInputStream( ByteArrayInputStream in2 = new ByteArrayInputStream(
@@ -676,58 +678,58 @@ public class JsonParserTest {
} }
@Test @Test
public void testEncodingUTF8() throws JsonParserException { void encodingUTF8() throws JsonParserException {
testEncoding(UTF8); testEncoding(UTF8);
testEncodingBOM(UTF8); testEncodingBOM(UTF8);
} }
@Test @Test
public void testEncodingUTF16LE() throws JsonParserException { void encodingUTF16LE() throws JsonParserException {
Charset charset = Charset.forName("UTF-16LE"); Charset charset = Charset.forName("UTF-16LE");
testEncoding(charset); testEncoding(charset);
testEncodingBOM(charset); testEncodingBOM(charset);
} }
@Test @Test
public void testEncodingUTF16BE() throws JsonParserException { void encodingUTF16BE() throws JsonParserException {
Charset charset = Charset.forName("UTF-16BE"); Charset charset = Charset.forName("UTF-16BE");
testEncoding(charset); testEncoding(charset);
testEncodingBOM(charset); testEncodingBOM(charset);
} }
@Test @Test
public void testEncodingUTF32LE() throws JsonParserException { void encodingUTF32LE() throws JsonParserException {
Charset charset = Charset.forName("UTF-32LE"); Charset charset = Charset.forName("UTF-32LE");
testEncoding(charset); testEncoding(charset);
testEncodingBOM(charset); testEncodingBOM(charset);
} }
@Test @Test
public void testEncodingUTF32BE() throws JsonParserException { void encodingUTF32BE() throws JsonParserException {
Charset charset = Charset.forName("UTF-32BE"); Charset charset = Charset.forName("UTF-32BE");
testEncoding(charset); testEncoding(charset);
testEncodingBOM(charset); testEncodingBOM(charset);
} }
@Test @Test
public void testValidUTF8Codepoint() throws JsonParserException { void validUTF8Codepoint() throws JsonParserException {
assertEquals("\ud83d\ude8a", assertEquals("\ud83d\ude8a",
JsonParser.any().from(new ByteArrayInputStream("\"\ud83d\ude8a\"".getBytes(UTF8)))); JsonParser.any().from(new ByteArrayInputStream("\"\ud83d\ude8a\"".getBytes(UTF8))).toString());
} }
@Test @Test
public void testValidUTF8Codepoint2() throws JsonParserException { void validUTF8Codepoint2() throws JsonParserException {
assertEquals("\u2602", assertEquals("\u2602",
JsonParser.any().from(new ByteArrayInputStream("\"\u2602\"".getBytes(UTF8)))); JsonParser.any().from(new ByteArrayInputStream("\"\u2602\"".getBytes(UTF8))).toString());
} }
@Test @Test
public void testIllegalUTF8Bytes() { void illegalUTF8Bytes() {
// Test the always-illegal bytes // Test the always-illegal bytes
int[] failures = new int[] { 0xc0, 0xc1, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; int[] failures = new int[] { 0xc0, 0xc1, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
for (int i = 0; i < failures.length; i++) { for (int i = 0; i < failures.length; i++) {
try { try {
JsonParser.object().from(new ByteArrayInputStream(new byte[] { '"', (byte)failures[i], '"' })); JsonParser.object().from(new ByteArrayInputStream(new byte[] { '"', (byte) failures[i], '"' }));
} catch (JsonParserException e) { } catch (JsonParserException e) {
testException(e, 1, 2, "UTF-8"); testException(e, 1, 2, "UTF-8");
} }
@@ -736,7 +738,7 @@ public class JsonParserTest {
// Test the continuation bytes outside of a continuation // Test the continuation bytes outside of a continuation
for (int i = 0x80; i <= 0xBF; i++) { for (int i = 0x80; i <= 0xBF; i++) {
try { try {
JsonParser.object().from(new ByteArrayInputStream(new byte[] { '"', (byte)i, '"' })); JsonParser.object().from(new ByteArrayInputStream(new byte[] { '"', (byte) i, '"' }));
} catch (JsonParserException e) { } catch (JsonParserException e) {
testException(e, 1, 2, "UTF-8"); testException(e, 1, 2, "UTF-8");
} }
@@ -744,10 +746,11 @@ public class JsonParserTest {
} }
/** /**
* See http://seriot.ch/parsing_json.html and https://github.com/mmastrac/nanojson/issues/3. * See http://seriot.ch/parsing_json.html and
* https://github.com/mmastrac/nanojson/issues/3.
*/ */
@Test @Test
public void testIllegalUTF8StringFromJSONSuite() { void illegalUTF8StringFromJSONSuite() {
try { try {
JsonParser.object().from(new ByteArrayInputStream(new byte[] { JsonParser.object().from(new ByteArrayInputStream(new byte[] {
'"', (byte) 0xed, (byte) 0xa0, (byte) 0x80, '"' })); '"', (byte) 0xed, (byte) 0xa0, (byte) 0x80, '"' }));
@@ -774,7 +777,7 @@ public class JsonParserTest {
} }
@Test @Test
public void failureTestsFromYui() throws IOException { void failureTestsFromYui() throws IOException {
InputStream input = getClass().getClassLoader().getResourceAsStream("yui_fail_cases.txt"); InputStream input = getClass().getClassLoader().getResourceAsStream("yui_fail_cases.txt");
String[] failCases = readAsUtf8(input).split("\n"); String[] failCases = readAsUtf8(input).split("\n");
@@ -789,7 +792,7 @@ public class JsonParserTest {
} }
@Test @Test
public void tortureTest() throws JsonParserException, IOException { void tortureTest() throws JsonParserException, IOException {
InputStream input = getClass().getClassLoader().getResourceAsStream("sample.json"); InputStream input = getClass().getClassLoader().getResourceAsStream("sample.json");
JsonObject o = JsonParser.object().from(readAsUtf8(input)); JsonObject o = JsonParser.object().from(readAsUtf8(input));
assertNotNull(o.get("a")); assertNotNull(o.get("a"));
@@ -803,26 +806,26 @@ public class JsonParserTest {
} }
@Test @Test
public void tortureTestUrl() throws JsonParserException { void tortureTestUrl() throws JsonParserException {
JsonObject o = JsonParser.object().from(getClass().getClassLoader().getResource("sample.json")); JsonObject o = JsonParser.object().from(getClass().getClassLoader().getResource("sample.json"));
assertNotNull(o.getObject("a").getArray("b\uecee\u8324\u007a\\\ue768.N")); assertNotNull(o.getObject("a").getArray("b\uecee\u8324\u007a\\\ue768.N"));
} }
@Test @Test
public void tortureTestStream() throws JsonParserException { void tortureTestStream() throws JsonParserException {
JsonObject o = JsonParser.object().from(getClass().getClassLoader().getResourceAsStream("sample.json")); JsonObject o = JsonParser.object().from(getClass().getClassLoader().getResourceAsStream("sample.json"));
assertNotNull(o.getObject("a").getArray("b\uecee\u8324\u007a\\\ue768.N")); assertNotNull(o.getObject("a").getArray("b\uecee\u8324\u007a\\\ue768.N"));
} }
@Test @Test
public void testIssue38() throws JsonParserException, IOException { void issue38() throws JsonParserException, IOException {
// https://github.com/mmastrac/nanojson/issues/38 // https://github.com/mmastrac/nanojson/issues/38
InputStream input = getClass().getClassLoader().getResourceAsStream("issue-38.json"); InputStream input = getClass().getClassLoader().getResourceAsStream("issue-38.json");
JsonParser.any().from(readAsUtf8(input)); JsonParser.any().from(readAsUtf8(input));
} }
@Test @Test
public void testEscapeSequencesAcrossBufferBoundary() throws JsonParserException { void escapeSequencesAcrossBufferBoundary() throws JsonParserException {
String s1 = ""; String s1 = "";
String s2 = ""; String s2 = "";
@@ -839,7 +842,7 @@ public class JsonParserTest {
} }
@Test @Test
public void testFailTruncatedEscapeAcrossBufferBoundary() { void failTruncatedEscapeAcrossBufferBoundary() {
String s1 = "\\u123"; String s1 = "\\u123";
String s2 = ""; String s2 = "";
for (int i = 0; i < 126; i++) { for (int i = 0; i < 126; i++) {
@@ -856,7 +859,7 @@ public class JsonParserTest {
JsonParser.object().from("\"" + s2 + s1); JsonParser.object().from("\"" + s2 + s1);
fail(); fail();
} catch (JsonParserException e) { } catch (JsonParserException e) {
assertTrue(e.getMessage(), e.getMessage().contains("EOF")); assertTrue(e.getMessage().contains("EOF"), e.getMessage());
} }
} }
} }
@@ -867,7 +870,7 @@ public class JsonParserTest {
* Skips two tests that don't match reality (ie: Chrome). * Skips two tests that don't match reality (ie: Chrome).
*/ */
@Test @Test
public void jsonOrgTest() throws IOException { void jsonOrgTest() throws IOException {
InputStream input = getClass().getClassLoader().getResourceAsStream("json_org_test.zip"); InputStream input = getClass().getClassLoader().getResourceAsStream("json_org_test.zip");
ZipInputStream zip = new ZipInputStream(input); ZipInputStream zip = new ZipInputStream(input);
ZipEntry ze; ZipEntry ze;
@@ -886,7 +889,7 @@ public class JsonParserTest {
boolean positive = ze.getName().startsWith("test/pass"); boolean positive = ze.getName().startsWith("test/pass");
int offset = 0; int offset = 0;
int size = (int)ze.getSize(); int size = (int) ze.getSize();
byte[] buffer = new byte[size]; byte[] buffer = new byte[size];
while (size > 0) { while (size > 0) {
int r = zip.read(buffer, offset, buffer.length - offset); int r = zip.read(buffer, offset, buffer.length - offset);
@@ -931,14 +934,14 @@ public class JsonParserTest {
} }
private void testException(JsonParserException e, int linePos, int charPos) { private void testException(JsonParserException e, int linePos, int charPos) {
assertEquals(e.getMessage() + " incorrect location", assertEquals("line " + linePos + " char " + charPos,
"line " + linePos + " char " + charPos, "line " + e.getLinePosition() + " char " + e.getCharPosition(),
"line " + e.getLinePosition() + " char " + e.getCharPosition()); e.getMessage() + " incorrect location");
} }
private void testException(JsonParserException e, int linePos, int charPos, String inError) { private void testException(JsonParserException e, int linePos, int charPos, String inError) {
assertEquals("line " + linePos + " char " + charPos, assertEquals("line " + linePos + " char " + charPos,
"line " + e.getLinePosition() + " char " + e.getCharPosition()); "line " + e.getLinePosition() + " char " + e.getCharPosition());
assertTrue("Error did not contain '" + inError + "': " + e.getMessage(), e.getMessage().contains(inError)); assertTrue(e.getMessage().contains(inError), "Error did not contain '" + inError + "': " + e.getMessage());
} }
} }

View File

@@ -15,9 +15,9 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -25,9 +25,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import org.junit.Test;
import com.grack.nanojson.Users.Friend; import com.grack.nanojson.Users.Friend;
import org.junit.jupiter.api.Test;
import com.grack.nanojson.Users.User; import com.grack.nanojson.Users.User;
/** /**
@@ -39,7 +39,7 @@ public class JsonReaderTest {
* Read a simple object. * Read a simple object.
*/ */
@Test @Test
public void testObject() throws JsonParserException { void object() throws JsonParserException {
JsonReader reader = JsonReader.from("{\"a\":1}"); JsonReader reader = JsonReader.from("{\"a\":1}");
assertEquals(JsonReader.Type.OBJECT, reader.current()); assertEquals(JsonReader.Type.OBJECT, reader.current());
reader.object(); reader.object();
@@ -54,7 +54,7 @@ public class JsonReaderTest {
* Read a simple array. * Read a simple array.
*/ */
@Test @Test
public void testArray() throws JsonParserException { void array() throws JsonParserException {
JsonReader reader = JsonReader.from("[\"a\",1,null]"); JsonReader reader = JsonReader.from("[\"a\",1,null]");
assertEquals(JsonReader.Type.ARRAY, reader.current()); assertEquals(JsonReader.Type.ARRAY, reader.current());
reader.array(); reader.array();
@@ -77,7 +77,7 @@ public class JsonReaderTest {
* Assert all the things. * Assert all the things.
*/ */
@Test @Test
public void testNestedDetailed() throws JsonParserException { void nestedDetailed() throws JsonParserException {
String json = createNestedJson(); String json = createNestedJson();
JsonReader reader = JsonReader.from(json); JsonReader reader = JsonReader.from(json);
@@ -128,11 +128,11 @@ public class JsonReaderTest {
} }
/** /**
* Same test as {@link JsonReaderTest#testNestedDetailed()}, less assertions to get a better * Same test as {@link JsonReaderTest#nestedDetailed()}, less assertions to get a better
* feel for the API. * feel for the API.
*/ */
@Test @Test
public void testNestedLight() throws JsonParserException { void nestedLight() throws JsonParserException {
String json = createNestedJson(); String json = createNestedJson();
JsonReader reader = JsonReader.from(json); JsonReader reader = JsonReader.from(json);
@@ -176,7 +176,7 @@ public class JsonReaderTest {
* Test reading an multiple arrays (including an empty one) in a object. * Test reading an multiple arrays (including an empty one) in a object.
*/ */
@Test @Test
public void testArraysInObject() throws JsonParserException { void arraysInObject() throws JsonParserException {
String json = createArraysInObject(); String json = createArraysInObject();
JsonReader reader = JsonReader.from(json); JsonReader reader = JsonReader.from(json);
@@ -213,7 +213,7 @@ public class JsonReaderTest {
* Test the {@link Users} class from java-json-benchmark. * Test the {@link Users} class from java-json-benchmark.
*/ */
@Test @Test
public void testJsonBenchmarkUser() throws JsonParserException { void jsonBenchmarkUser() throws JsonParserException {
JsonReader reader = JsonReader.from(getClass().getResourceAsStream("/users.json")); JsonReader reader = JsonReader.from(getClass().getResourceAsStream("/users.json"));
parseUsers(reader); parseUsers(reader);

View File

@@ -15,24 +15,26 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.Assert.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.junit.jupiter.api.Test;
/** /**
* Test for the various JSON types. * Test for the various JSON types.
*/ */
public class JsonTypesTest { class JsonTypesTest {
// CHECKSTYLE_OFF: MagicNumber // CHECKSTYLE_OFF: MagicNumber
// CHECKSTYLE_OFF: JavadocMethod // CHECKSTYLE_OFF: JavadocMethod
@Test @Test
public void testObjectInt() { void objectInt() {
JsonObject o = new JsonObject(); JsonObject o = new JsonObject();
o.put("key", 1); o.put("key", 1);
assertEquals(1, o.getInt("key")); assertEquals(1, o.getInt("key"));
@@ -42,39 +44,39 @@ public class JsonTypesTest {
assertEquals(1, o.getNumber("key")); assertEquals(1, o.getNumber("key"));
assertEquals(1, o.get("key")); assertEquals(1, o.get("key"));
assertEquals(null, o.getString("key")); assertNull(o.getString("key"));
assertEquals("foo", o.getString("key", "foo")); assertEquals("foo", o.getString("key", "foo"));
assertFalse(o.isNull("key")); assertFalse(o.isNull("key"));
} }
@Test @Test
public void testObjectString() { void objectString() {
JsonObject o = new JsonObject(); JsonObject o = new JsonObject();
o.put("key", "1"); o.put("key", "1");
assertEquals(0, o.getInt("key")); assertEquals(0, o.getInt("key"));
assertEquals(0L, o.getLong("key")); assertEquals(0L, o.getLong("key"));
assertEquals(0, o.getDouble("key"), 0.0001f); assertEquals(0, o.getDouble("key"), 0.0001f);
assertEquals(0f, o.getFloat("key"), 0.0001f); assertEquals(0f, o.getFloat("key"), 0.0001f);
assertEquals(null, o.getNumber("key")); assertNull(o.getNumber("key"));
assertEquals("1", o.get("key")); assertEquals("1", o.get("key"));
assertFalse(o.isNull("key")); assertFalse(o.isNull("key"));
} }
@Test @Test
public void testObjectNull() { void objectNull() {
JsonObject o = new JsonObject(); JsonObject o = new JsonObject();
o.put("key", null); o.put("key", null);
assertEquals(0, o.getInt("key")); assertEquals(0, o.getInt("key"));
assertEquals(0L, o.getLong("key")); assertEquals(0L, o.getLong("key"));
assertEquals(0, o.getDouble("key"), 0.0001f); assertEquals(0, o.getDouble("key"), 0.0001f);
assertEquals(0f, o.getFloat("key"), 0.0001f); assertEquals(0f, o.getFloat("key"), 0.0001f);
assertEquals(null, o.getNumber("key")); assertNull(o.getNumber("key"));
assertEquals(null, o.get("key")); assertNull(o.get("key"));
assertTrue(o.isNull("key")); assertTrue(o.isNull("key"));
} }
@Test @Test
public void testArrayInt() { void arrayInt() {
JsonArray o = new JsonArray(Arrays.asList((String) null, null, null, JsonArray o = new JsonArray(Arrays.asList((String) null, null, null,
null)); null));
o.set(3, 1); o.set(3, 1);
@@ -85,13 +87,13 @@ public class JsonTypesTest {
assertEquals(1, o.getNumber(3)); assertEquals(1, o.getNumber(3));
assertEquals(1, o.get(3)); assertEquals(1, o.get(3));
assertEquals(null, o.getString(3)); assertNull(o.getString(3));
assertEquals("foo", o.getString(3, "foo")); assertEquals("foo", o.getString(3, "foo"));
assertFalse(o.isNull(3)); assertFalse(o.isNull(3));
} }
@Test @Test
public void testArrayString() { void arrayString() {
JsonArray o = new JsonArray(Arrays.asList((String) null, null, null, JsonArray o = new JsonArray(Arrays.asList((String) null, null, null,
null)); null));
o.set(3, "1"); o.set(3, "1");
@@ -99,40 +101,40 @@ public class JsonTypesTest {
assertEquals(0L, o.getLong(3)); assertEquals(0L, o.getLong(3));
assertEquals(0, o.getDouble(3), 0.0001f); assertEquals(0, o.getDouble(3), 0.0001f);
assertEquals(0, o.getFloat(3), 0.0001f); assertEquals(0, o.getFloat(3), 0.0001f);
assertEquals(null, o.getNumber(3)); assertNull(o.getNumber(3));
assertEquals("1", o.get(3)); assertEquals("1", o.get(3));
assertFalse(o.isNull(3)); assertFalse(o.isNull(3));
} }
@Test @Test
public void testArrayNull() { void arrayNull() {
JsonArray o = new JsonArray(Arrays.asList((String) null, null, null, JsonArray o = new JsonArray(Arrays.asList((String) null, null, null,
null)); null));
o.set(3, null); o.set(3, null);
assertEquals(0, o.getInt(3)); assertEquals(0, o.getInt(3));
assertEquals(0, o.getDouble(3), 0.0001f); assertEquals(0, o.getDouble(3), 0.0001f);
assertEquals(0, o.getFloat(3), 0.0001f); assertEquals(0, o.getFloat(3), 0.0001f);
assertEquals(null, o.getNumber(3)); assertNull(o.getNumber(3));
assertEquals(null, o.get(3)); assertNull(o.get(3));
assertTrue(o.isNull(3)); assertTrue(o.isNull(3));
assertTrue(o.has(3)); assertTrue(o.has(3));
} }
@Test @Test
public void testArrayBounds() { void arrayBounds() {
JsonArray o = new JsonArray(Arrays.asList((String) null, null, null, JsonArray o = new JsonArray(Arrays.asList((String) null, null, null,
null)); null));
assertEquals(0, o.getInt(4)); assertEquals(0, o.getInt(4));
assertEquals(0, o.getDouble(4), 0.0001f); assertEquals(0, o.getDouble(4), 0.0001f);
assertEquals(0, o.getFloat(4), 0.0001f); assertEquals(0, o.getFloat(4), 0.0001f);
assertEquals(null, o.getNumber(4)); assertNull(o.getNumber(4));
assertEquals(null, o.get(4)); assertNull(o.get(4));
assertFalse(o.isNull(4)); assertFalse(o.isNull(4));
assertFalse(o.has(4)); assertFalse(o.has(4));
} }
@Test @Test
public void testJsonArrayBuilder() { void jsonArrayBuilder() {
// @formatter:off // @formatter:off
JsonArray a = JsonArray.builder().value(true).value(1.0).value(1.0f) JsonArray a = JsonArray.builder().value(true).value(1.0).value(1.0f)
.value(1).value(new BigInteger("1234567890")).value("hi") .value(1).value(new BigInteger("1234567890")).value("hi")
@@ -147,7 +149,7 @@ public class JsonTypesTest {
} }
@Test @Test
public void testJsonObjectBuilder() { void jsonObjectBuilder() {
// @formatter:off // @formatter:off
JsonObject a = JsonObject JsonObject a = JsonObject
.builder() .builder()
@@ -185,23 +187,26 @@ public class JsonTypesTest {
} }
} }
@Test(expected = JsonWriterException.class) @Test
public void testJsonArrayBuilderFailCantCloseRoot() { void jsonArrayBuilderFailCantCloseRoot() {
JsonArray.builder().end(); assertThrows(JsonWriterException.class, () ->
} JsonArray.builder().end());
@Test(expected = JsonWriterException.class)
public void testJsonArrayBuilderFailCantAddKeyToArray() {
JsonArray.builder().value("abc", 1);
}
@Test(expected = JsonWriterException.class)
public void testJsonArrayBuilderFailCantAddNonKeyToObject() {
JsonObject.builder().value(1);
} }
@Test @Test
public void testJsonKeyOrder() { void jsonArrayBuilderFailCantAddKeyToArray() {
assertThrows(JsonWriterException.class, () ->
JsonArray.builder().value("abc", 1));
}
@Test
void jsonArrayBuilderFailCantAddNonKeyToObject() {
assertThrows(JsonWriterException.class, () ->
JsonObject.builder().value(1));
}
@Test
void jsonKeyOrder() {
JsonObject a = JsonObject JsonObject a = JsonObject
.builder() .builder()
.value("key01", 1) .value("key01", 1)

View File

@@ -15,9 +15,7 @@
*/ */
package com.grack.nanojson; package com.grack.nanojson;
import static org.junit.Assert.assertEquals; import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
@@ -25,25 +23,32 @@ import java.io.PrintStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import org.junit.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/** /**
* Test for {@link JsonWriter}. * Test for {@link JsonWriter}.
*/ */
public class JsonWriterTest { class JsonWriterTest {
private static final Charset UTF8 = StandardCharsets.UTF_8; private static final Charset UTF8 = StandardCharsets.UTF_8;
// CHECKSTYLE_OFF: MagicNumber // CHECKSTYLE_OFF: MagicNumber
// CHECKSTYLE_OFF: JavadocMethod // CHECKSTYLE_OFF: JavadocMethod
// CHECKSTYLE_OFF: EmptyBlock // CHECKSTYLE_OFF: EmptyBlock
/** /**
* Test emitting simple values. * Test emitting simple values.
*/ */
@Test @Test
public void testSimpleValues() { void simpleValues() {
assertEquals("true", JsonWriter.string().value(true).done()); assertEquals("true", JsonWriter.string().value(true).done());
assertEquals("null", JsonWriter.string().nul().done()); assertEquals("null", JsonWriter.string().nul().done());
assertEquals("1.0", JsonWriter.string().value(1.0).done()); assertEquals("1.0", JsonWriter.string().value(1.0).done());
@@ -57,16 +62,16 @@ public class JsonWriterTest {
* exception. * exception.
*/ */
@Test @Test
public void testStreamWriterWithNonBMPStringAroundBufferSize() throws JsonParserException { void streamWriterWithNonBMPStringAroundBufferSize() throws JsonParserException {
char[] c = new char[JsonWriterBase.BUFFER_SIZE - 128]; char[] c = new char[JsonWriterBase.BUFFER_SIZE - 128];
Arrays.fill(c, ' '); Arrays.fill(c, ' ');
String base = new String(c); StringBuilder base = new StringBuilder(new String(c));
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
base += " "; base.append(" ");
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
String s = base + new String(new int[] { 0x10ffff }, 0, 1); String s = base + new String(new int[]{0x10ffff}, 0, 1);
JsonWriter.on(bytes).value(s).done(); JsonWriter.on(bytes).value(s).done();
assertEquals(s, JsonParser.any().from(new String(bytes.toByteArray(), UTF8))); assertEquals(s, ((LazyString) JsonParser.any().from(new String(bytes.toByteArray(), UTF8))).toString());
} }
} }
@@ -75,16 +80,17 @@ public class JsonWriterTest {
* exception. * exception.
*/ */
@Test @Test
public void testStreamWriterWithBMPStringAroundBufferSize() throws JsonParserException { void streamWriterWithBMPStringAroundBufferSize() throws JsonParserException {
char[] c = new char[JsonWriterBase.BUFFER_SIZE - 128]; char[] c = new char[JsonWriterBase.BUFFER_SIZE - 128];
Arrays.fill(c, ' '); Arrays.fill(c, ' ');
String base = new String(c); String base = new String(c);
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
base += " "; base += " ";
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
String s = base + new String(new int[] { 0xffff }, 0, 1); String s = base + new String(new int[]{0xffff}, 0, 1);
JsonWriter.on(bytes).value(s).done(); JsonWriter.on(bytes).value(s).done();
assertEquals(s, JsonParser.any().from(new String(bytes.toByteArray(), UTF8))); Object parsed = JsonParser.any().from(new String(bytes.toByteArray(), UTF8));
assertEquals(s, parsed instanceof LazyString ? parsed.toString() : parsed);
} }
} }
@@ -93,19 +99,20 @@ public class JsonWriterTest {
* boundary exception. * boundary exception.
*/ */
@Test @Test
public void testStreamWriterWithArrayAroundBufferSize() throws JsonParserException { void streamWriterWithArrayAroundBufferSize() throws JsonParserException {
char[] c = new char[JsonWriterBase.BUFFER_SIZE - 128]; char[] c = new char[JsonWriterBase.BUFFER_SIZE - 128];
Arrays.fill(c, ' '); Arrays.fill(c, ' ');
String base = new String(c); String base = new String(c);
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
base += " "; base += " ";
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
String s = base + new String(new int[] { 0x10ffff }, 0, 1); String s = base + new String(new int[]{0x10ffff}, 0, 1);
JsonWriter.on(bytes).array().value(s).nul().end().done(); JsonWriter.on(bytes).array().value(s).nul().end().done();
String s2 = new String(bytes.toByteArray(), UTF8); String s2 = new String(bytes.toByteArray(), UTF8);
JsonArray array = JsonParser.array().from(s2); JsonArray array = JsonParser.array().from(s2);
assertEquals(s, array.get(0)); Object first = array.get(0);
assertEquals(null, array.get(1)); assertEquals(s, first instanceof LazyString ? first.toString() : first);
assertNull(array.get(1));
} }
} }
@@ -113,7 +120,7 @@ public class JsonWriterTest {
* Test various ways of writing null, as well as various situations. * Test various ways of writing null, as well as various situations.
*/ */
@Test @Test
public void testNull() { void testNull() {
assertEquals("null", JsonWriter.string().value((String) null).done()); assertEquals("null", JsonWriter.string().value((String) null).done());
assertEquals("null", JsonWriter.string().value((Number) null).done()); assertEquals("null", JsonWriter.string().value((Number) null).done());
assertEquals("null", JsonWriter.string().nul().done()); assertEquals("null", JsonWriter.string().nul().done());
@@ -132,11 +139,21 @@ public class JsonWriterTest {
.end().done()); .end().done());
} }
@Test
void separateKeyWriting() {
assertEquals("{\"a\":null}",
JsonWriter.string().object().key("a").value((Number) null).end()
.done());
assertEquals("{\"a\":{\"b\":null}}",
JsonWriter.string().object().key("a").object().value("b", (Number) null)
.end().end().done());
}
/** /**
* Test escaping of chars < 256. * Test escaping of chars < 256.
*/ */
@Test @Test
public void testStringControlCharacters() { void stringControlCharacters() {
StringBuilder chars = new StringBuilder(); StringBuilder chars = new StringBuilder();
for (int i = 0; i < 0xa0; i++) for (int i = 0; i < 0xa0; i++)
chars.append((char) i); chars.append((char) i);
@@ -157,7 +174,7 @@ public class JsonWriterTest {
* Test escaping of chars < 256. * Test escaping of chars < 256.
*/ */
@Test @Test
public void testEscape() { void escape() {
StringBuilder chars = new StringBuilder(); StringBuilder chars = new StringBuilder();
for (int i = 0; i < 0xa0; i++) for (int i = 0; i < 0xa0; i++)
chars.append((char) i); chars.append((char) i);
@@ -177,10 +194,10 @@ public class JsonWriterTest {
* Torture test for UTF8 character encoding. * Torture test for UTF8 character encoding.
*/ */
@Test @Test
public void testBMPCharacters() throws Exception { void bmpCharacters() throws Exception {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (int i = 0; i < 0xD000; i++) { for (int i = 0; i < 0xD000; i++) {
builder.append((char)i); builder.append((char) i);
} }
builder.append("\ue000"); builder.append("\ue000");
builder.append("\uefff"); builder.append("\uefff");
@@ -189,26 +206,28 @@ public class JsonWriterTest {
// Base string // Base string
String s = JsonWriter.string(builder.toString()); String s = JsonWriter.string(builder.toString());
assertEquals(builder.toString(), (String)JsonParser.any().from(s)); Object parsed = JsonParser.any().from(s);
assertEquals(builder.toString(), parsed instanceof LazyString ? parsed.toString() : parsed);
// Ensure that it also matches the PrintStream output // Ensure that it also matches the PrintStream output
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
JsonWriter.on(new PrintStream(bytes, false, "UTF-8")).value(builder.toString()).done(); JsonWriter.on(new PrintStream(bytes, false, "UTF-8")).value(builder.toString()).done();
assertEquals(builder.toString(), (String)JsonParser.any().from(new String(bytes.toByteArray(), parsed = JsonParser.any().from(new String(bytes.toByteArray(), UTF8));
UTF8))); assertEquals(builder.toString(), parsed instanceof LazyString ? parsed.toString() : parsed);
// Ensure that it also matches the stream output // Ensure that it also matches the stream output
bytes = new ByteArrayOutputStream(); bytes = new ByteArrayOutputStream();
JsonWriter.on(bytes).value(builder.toString()).done(); JsonWriter.on(bytes).value(builder.toString()).done();
assertEquals(builder.toString(), (String)JsonParser.any().from(new String(bytes.toByteArray(), parsed = JsonParser.any().from(new String(bytes.toByteArray(), UTF8));
UTF8))); assertEquals(builder.toString(), parsed instanceof LazyString ? parsed.toString() : parsed);
} }
/** /**
* Torture test for UTF8 character encoding outside the basic multilingual plane. * Torture test for UTF8 character encoding outside the basic multilingual
* plane.
*/ */
@Test @Test
public void testNonBMP() throws Exception { void nonBMP() throws Exception {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.appendCodePoint(0x10000); // Start of non-BMP builder.appendCodePoint(0x10000); // Start of non-BMP
builder.appendCodePoint(0x1f601); // GRINNING FACE WITH SMILING EYES builder.appendCodePoint(0x1f601); // GRINNING FACE WITH SMILING EYES
@@ -216,26 +235,26 @@ public class JsonWriterTest {
// Base string // Base string
String s = JsonWriter.string(builder.toString()); String s = JsonWriter.string(builder.toString());
assertEquals(builder.toString(), (String)JsonParser.any().from(s)); assertEquals(builder.toString(), ((LazyString) JsonParser.any().from(s)).toString());
// Ensure that it also matches the PrintStream output // Ensure that it also matches the PrintStream output
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
JsonWriter.on(new PrintStream(bytes, false, "UTF-8")).value(builder.toString()).done(); JsonWriter.on(new PrintStream(bytes, false, "UTF-8")).value(builder.toString()).done();
assertEquals(builder.toString(), (String)JsonParser.any().from(new String(bytes.toByteArray(), assertEquals(builder.toString(), ((LazyString) JsonParser.any().from(new String(bytes.toByteArray(),
UTF8))); UTF8))).toString());
// Ensure that it also matches the stream output // Ensure that it also matches the stream output
bytes = new ByteArrayOutputStream(); bytes = new ByteArrayOutputStream();
JsonWriter.on(bytes).value(builder.toString()).done(); JsonWriter.on(bytes).value(builder.toString()).done();
assertEquals(builder.toString(), (String)JsonParser.any().from(new String(bytes.toByteArray(), assertEquals(builder.toString(), ((LazyString) JsonParser.any().from(new String(bytes.toByteArray(),
UTF8))); UTF8))).toString());
} }
/** /**
* Basic {@link OutputStream} smoke test. * Basic {@link OutputStream} smoke test.
*/ */
@Test @Test
public void testWriteToUTF8Stream() { void writeToUTF8Stream() {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
JsonWriter.on(bytes).object().value("a\n", 1) JsonWriter.on(bytes).object().value("a\n", 1)
.value("b", 2).end().done(); .value("b", 2).end().done();
@@ -247,7 +266,7 @@ public class JsonWriterTest {
* Basic {@link PrintStream} smoke test. * Basic {@link PrintStream} smoke test.
*/ */
@Test @Test
public void testWriteToSystemOutLikeStream() throws Exception { void writeToSystemOutLikeStream() throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ByteArrayOutputStream bytes = new ByteArrayOutputStream();
JsonWriter.on(new PrintStream(bytes, false, "UTF-8")).object().value("a\n", 1) JsonWriter.on(new PrintStream(bytes, false, "UTF-8")).object().value("a\n", 1)
.value("b", 2).end().done(); .value("b", 2).end().done();
@@ -260,7 +279,7 @@ public class JsonWriterTest {
* Test escaping of / when following < to handle &lt;/script&gt;. * Test escaping of / when following < to handle &lt;/script&gt;.
*/ */
@Test @Test
public void testScriptEndEscaping() { void scriptEndEscaping() {
assertEquals("\"<\\/script>\"", JsonWriter.string("</script>")); assertEquals("\"<\\/script>\"", JsonWriter.string("</script>"));
assertEquals("\"/script\"", JsonWriter.string("/script")); assertEquals("\"/script\"", JsonWriter.string("/script"));
} }
@@ -269,7 +288,7 @@ public class JsonWriterTest {
* Test a simple array. * Test a simple array.
*/ */
@Test @Test
public void testArray() { void array() {
String json = JsonWriter.string().array().value(true).value(false) String json = JsonWriter.string().array().value(true).value(false)
.value(true).end().done(); .value(true).end().done();
assertEquals("[true,false,true]", json); assertEquals("[true,false,true]", json);
@@ -279,16 +298,59 @@ public class JsonWriterTest {
* Test an empty array. * Test an empty array.
*/ */
@Test @Test
public void testArrayEmpty() { void arrayEmpty() {
String json = JsonWriter.string().array().end().done(); String json = JsonWriter.string().array().end().done();
assertEquals("[]", json); assertEquals("[]", json);
} }
/**
* Test the auto-conversion of Writables.
*/
@Test
void writable() {
assertEquals("null", JsonWriter.string((JsonConvertible) () -> null));
assertEquals("[]", JsonWriter.string((JsonConvertible) ArrayList::new));
assertEquals("{}", JsonWriter.string((JsonConvertible) HashMap::new));
assertEquals("\"\"", JsonWriter.string((JsonConvertible) () -> ""));
assertEquals("1", JsonWriter.string((JsonConvertible) () -> Integer.valueOf(1)));
assertEquals("1.0", JsonWriter.string((JsonConvertible) () -> Double.valueOf(1.0)));
assertEquals("1", JsonWriter.string((JsonConvertible) () -> Long.valueOf(1)));
assertEquals("1.0", JsonWriter.string((JsonConvertible) () -> Float.valueOf(1.0f)));
assertEquals(
"[null,[1,2,3],{\"a\":1,\"b\":2.0,\"c\":\"a\",\"d\":null,\"e\":[]}]",
JsonWriter.string((JsonConvertible) () -> (JsonConvertible) () -> {
ArrayList<Object> list = new ArrayList<>();
list.add(null);
list.add((JsonConvertible) () -> new int[]{1, 2, 3});
list.add((JsonConvertible) () -> {
HashMap<String, Object> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2.0);
map.put("c", "a");
map.put("d", null);
map.put("e", (JsonConvertible) ArrayList::new);
return map;
});
return list;
}));
assertEquals(
"Unable to handle type: class java.lang.Object",
assertThrows(
JsonWriterException.class,
() -> JsonWriter.string((JsonConvertible) Object::new)).getMessage());
assertEquals(
"Unable to handle type: class java.lang.Object",
assertThrows(
JsonWriterException.class,
() -> JsonWriter.string((JsonConvertible) () -> Arrays.asList("d", 1, new Object())))
.getMessage());
}
/** /**
* Test an array of empty arrays. * Test an array of empty arrays.
*/ */
@Test @Test
public void testArrayOfEmpty() { void arrayOfEmpty() {
String json = JsonWriter.string().array().array().end().array().end() String json = JsonWriter.string().array().array().end().array().end()
.end().done(); .end().done();
assertEquals("[[],[]]", json); assertEquals("[[],[]]", json);
@@ -298,7 +360,7 @@ public class JsonWriterTest {
* Test a nested array. * Test a nested array.
*/ */
@Test @Test
public void testNestedArray() { void nestedArray() {
String json = JsonWriter.string().array().array().array().value(true) String json = JsonWriter.string().array().array().array().value(true)
.value(false).value(true).end().end().end().done(); .value(false).value(true).end().end().end().done();
assertEquals("[[[true,false,true]]]", json); assertEquals("[[[true,false,true]]]", json);
@@ -308,7 +370,7 @@ public class JsonWriterTest {
* Test a nested array. * Test a nested array.
*/ */
@Test @Test
public void testNestedArray2() { void nestedArray2() {
String json = JsonWriter.string().array().value(true).array().array() String json = JsonWriter.string().array().value(true).array().array()
.value(false).end().end().value(true).end().done(); .value(false).end().end().value(true).end().done();
assertEquals("[true,[[false]],true]", json); assertEquals("[true,[[false]],true]", json);
@@ -318,7 +380,7 @@ public class JsonWriterTest {
* Test a simple object. * Test a simple object.
*/ */
@Test @Test
public void testObject() { void object() {
String json = JsonWriter.string().object().value("a", true) String json = JsonWriter.string().object().value("a", true)
.value("b", false).value("c", true).end().done(); .value("b", false).value("c", true).end().done();
assertEquals("{\"a\":true,\"b\":false,\"c\":true}", json); assertEquals("{\"a\":true,\"b\":false,\"c\":true}", json);
@@ -328,7 +390,7 @@ public class JsonWriterTest {
* Test a simple object with indent. * Test a simple object with indent.
*/ */
@Test @Test
public void testObjectIndent() { void objectIndent() {
String json = JsonWriter.indent(" ").string().object() String json = JsonWriter.indent(" ").string().object()
.value("a", true).value("b", false).value("c", true).end() .value("a", true).value("b", false).value("c", true).end()
.done(); .done();
@@ -339,7 +401,7 @@ public class JsonWriterTest {
* Test a nested object. * Test a nested object.
*/ */
@Test @Test
public void testNestedObject() { void nestedObject() {
String json = JsonWriter.string().object().object("a") String json = JsonWriter.string().object().object("a")
.value("b", false).value("c", true).end().end().done(); .value("b", false).value("c", true).end().end().done();
assertEquals("{\"a\":{\"b\":false,\"c\":true}}", json); assertEquals("{\"a\":{\"b\":false,\"c\":true}}", json);
@@ -349,7 +411,7 @@ public class JsonWriterTest {
* Test a nested object and array. * Test a nested object and array.
*/ */
@Test @Test
public void testNestedObjectArray() { void nestedObjectArray() {
//@formatter:off //@formatter:off
String json = JsonWriter.string() String json = JsonWriter.string()
.object() .object()
@@ -378,7 +440,7 @@ public class JsonWriterTest {
* Test a nested object and array. * Test a nested object and array.
*/ */
@Test @Test
public void testNestedObjectArrayIndent() { void nestedObjectArrayIndent() {
//@formatter:off //@formatter:off
String json = JsonWriter.indent(" ").string() String json = JsonWriter.indent(" ").string()
.object() .object()
@@ -402,14 +464,15 @@ public class JsonWriterTest {
assertEquals( assertEquals(
"{\n \"a\":{\n \"b\":[{\n \"a\":1,\n \"b\":2\n },{\n" "{\n \"a\":{\n \"b\":[{\n \"a\":1,\n \"b\":2\n },{\n"
+ " \"c\":1.0,\n \"d\":2.0\n }],\n" + " \"c\":1.0,\n \"d\":2.0\n }],\n"
+ " \"c\":[\"a\",\"b\",\"c\"]\n }\n}", json); + " \"c\":[\"a\",\"b\",\"c\"]\n }\n}",
json);
} }
/** /**
* Tests the {@link Appendable} code. * Tests the {@link Appendable} code.
*/ */
@Test @Test
public void testAppendable() { void appendable() {
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
JsonWriter.on(writer).object().value("abc", "def").end().done(); JsonWriter.on(writer).object().value("abc", "def").end().done();
assertEquals("{\"abc\":\"def\"}", writer.toString()); assertEquals("{\"abc\":\"def\"}", writer.toString());
@@ -419,7 +482,7 @@ public class JsonWriterTest {
* Tests the {@link OutputStream} code. * Tests the {@link OutputStream} code.
*/ */
@Test @Test
public void testOutputStream() { void outputStream() {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
JsonWriter.on(out).object().value("abc", "def").end().done(); JsonWriter.on(out).object().value("abc", "def").end().done();
assertEquals("{\"abc\":\"def\"}", assertEquals("{\"abc\":\"def\"}",
@@ -427,64 +490,64 @@ public class JsonWriterTest {
} }
@Test @Test
public void testQuickJson() { void quickJson() {
assertEquals("true", JsonWriter.string(true)); assertEquals("true", JsonWriter.string(true));
} }
@Test @Test
public void testQuickJsonArray() { void quickJsonArray() {
assertEquals("[1,2,3]", JsonWriter.string(JsonArray.from(1, 2, 3))); assertEquals("[1,2,3]", JsonWriter.string(JsonArray.from(1, 2, 3)));
} }
@Test @Test
public void testQuickArray() { void quickArray() {
assertEquals("[1,2,3]", JsonWriter.string(Arrays.asList(1, 2, 3))); assertEquals("[1,2,3]", JsonWriter.string(Arrays.asList(1, 2, 3)));
} }
@Test @Test
public void testQuickArrayEmpty() { void quickArrayEmpty() {
assertEquals("[]", JsonWriter.string(Collections.emptyList())); assertEquals("[]", JsonWriter.string(Collections.emptyList()));
} }
@Test @Test
public void testQuickObjectArray() { void quickObjectArray() {
assertEquals("[1,2,3]", JsonWriter.string(new Object[] { 1, 2, 3 })); assertEquals("[1,2,3]", JsonWriter.string(new Object[]{1, 2, 3}));
} }
@Test @Test
public void testQuickObjectArrayNested() { void quickObjectArrayNested() {
assertEquals( assertEquals(
"[[1,2],[[3]]]", "[[1,2],[[3]]]",
JsonWriter.string(new Object[] { new Object[] { 1, 2 }, JsonWriter.string(new Object[]{new Object[]{1, 2},
new Object[] { new Object[] { 3 } } })); new Object[]{new Object[]{3}}}));
} }
@Test @Test
public void testQuickObjectArrayEmpty() { void quickObjectArrayEmpty() {
assertEquals("[]", JsonWriter.string(new Object[0])); assertEquals("[]", JsonWriter.string(new Object[0]));
} }
@Test @Test
public void testObjectArrayInMap() { void objectArrayInMap() {
JsonObject o = new JsonObject(); JsonObject o = new JsonObject();
o.put("array of string", new String[] { "a", "b", "c" }); o.put("array of string", new String[]{"a", "b", "c"});
o.put("array of Boolean", new Boolean[] { true, false }); o.put("array of Boolean", new Boolean[]{true, false});
o.put("array of int", new int[] { 1, 2, 3 }); o.put("array of int", new int[]{1, 2, 3});
o.put("array of JsonObject", o.put("array of JsonObject",
new JsonObject[] { new JsonObject(), null }); new JsonObject[]{new JsonObject(), null});
String[] bits = { "\"array of JsonObject\":[{},null]", String[] bits = {"\"array of JsonObject\":[{},null]",
"\"array of Boolean\":[true,false]", "\"array of Boolean\":[true,false]",
"\"array of string\":[\"a\",\"b\",\"c\"]", "\"array of string\":[\"a\",\"b\",\"c\"]",
"\"array of int\":[1,2,3]" }; "\"array of int\":[1,2,3]"};
String s = JsonWriter.string(o); String s = JsonWriter.string(o);
for (String bit : bits) { for (String bit : bits) {
assertTrue("Didn't contain " + bit, s.contains(bit)); assertTrue(s.contains(bit), "Didn't contain " + bit);
} }
} }
@Test @Test
public void testFailureNoKeyInObject() { void failureNoKeyInObject() {
try { try {
JsonWriter.string().object().value(true).end().done(); JsonWriter.string().object().value(true).end().done();
fail(); fail();
@@ -494,7 +557,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureNoKeyInObject2() { void failureNoKeyInObject2() {
try { try {
JsonWriter.string().object().value("a", 1).value(true).end().done(); JsonWriter.string().object().value("a", 1).value(true).end().done();
fail(); fail();
@@ -504,7 +567,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureKeyInArray() { void failureKeyInArray() {
try { try {
JsonWriter.string().array().value("x", true).end().done(); JsonWriter.string().array().value("x", true).end().done();
fail(); fail();
@@ -514,7 +577,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureKeyInArray2() { void failureKeyInArray2() {
try { try {
JsonWriter.string().array().value(1).value("x", true).end().done(); JsonWriter.string().array().value(1).value("x", true).end().done();
fail(); fail();
@@ -524,7 +587,17 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureNotFullyClosed() { void failureRepeatedKey() {
assertThrows(JsonWriterException.class, () -> JsonWriter.string().object().key("a").value("b", 2).end().done());
}
@Test
void failureRepeatedKey2() {
assertThrows(JsonWriterException.class, () -> JsonWriter.string().object().key("a").key("b").end().done());
}
@Test
void failureNotFullyClosed() {
try { try {
JsonWriter.string().array().value(1).done(); JsonWriter.string().array().value(1).done();
fail(); fail();
@@ -534,7 +607,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureNotFullyClosed2() { void failureNotFullyClosed2() {
try { try {
JsonWriter.string().array().done(); JsonWriter.string().array().done();
fail(); fail();
@@ -544,7 +617,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureEmpty() { void failureEmpty() {
try { try {
JsonWriter.string().done(); JsonWriter.string().done();
fail(); fail();
@@ -554,7 +627,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureEmpty2() { void failureEmpty2() {
try { try {
JsonWriter.string().end(); JsonWriter.string().end();
fail(); fail();
@@ -564,7 +637,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureMoreThanOneRoot() { void failureMoreThanOneRoot() {
try { try {
JsonWriter.string().value(1).value(1).done(); JsonWriter.string().value(1).value(1).done();
fail(); fail();
@@ -574,7 +647,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureMoreThanOneRoot2() { void failureMoreThanOneRoot2() {
try { try {
JsonWriter.string().array().value(1).end().value(1).done(); JsonWriter.string().array().value(1).end().value(1).done();
fail(); fail();
@@ -584,7 +657,7 @@ public class JsonWriterTest {
} }
@Test @Test
public void testFailureMoreThanOneRoot3() { void failureMoreThanOneRoot3() {
try { try {
JsonWriter.string().array().value(1).end().array().value(1).end() JsonWriter.string().array().value(1).end().array().value(1).end()
.done(); .done();