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:
- 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.
- Various performance improvements borrowed from [@FireMasterK's fork](https://github.com/FireMasterK/nanojson).

View File

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

103
pom.xml
View File

@@ -13,19 +13,35 @@
<artifactId>nanojson</artifactId>
<packaging>jar</packaging>
<name>nanojson</name>
<version>1.8-SNAPSHOT</version>
<version>1.11-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</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>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<version>3.11.3</version>
<reportSets>
<reportSet>
<id>html</id>
@@ -44,7 +60,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version>
<version>3.6.0</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<propertyExpansion>basedir=${basedir}</propertyExpansion>
@@ -66,7 +82,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>2.22.2</version>
<version>3.5.3</version>
<reportSets>
<reportSet>
<reports>
@@ -77,21 +93,12 @@
</plugin>
</plugins>
</reporting>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<version>3.3.1</version>
<executions>
<execution>
<id>attach-sources</id>
@@ -104,7 +111,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<version>3.11.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -117,7 +124,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version>
<version>3.6.0</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<propertyExpansion>basedir=${basedir}</propertyExpansion>
@@ -145,7 +152,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<version>3.5.1</version>
<configuration>
<executable>java</executable>
@@ -165,15 +172,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.14.0</version>
<configuration>
<debug>true</debug>
<debuglevel>none</debuglevel>
<source>1.8</source>
<target>1.8</target>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
<debug>false</debug>
<release>11</release>
<compilerArgument>-Xlint:all</compilerArgument>
<compilerArguments>
<Werror />
@@ -183,21 +185,44 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.9.1</version>
<version>3.21.0</version>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>com.grack.nanojson</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<bnd><![CDATA[
Bundle-SymbolicName: ${groupId}.${artifactId}
Export-Package: com.grack.nanojson
-jpms-module-info: com.grack.nanojson
-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>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -210,7 +235,7 @@
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<version>1.7.0</version>
<extensions>true</extensions>
<configuration>
<serverId>sonatype-nexus-staging</serverId>
@@ -221,7 +246,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.0.1</version>
<version>3.2.8</version>
<executions>
<execution>
<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_) {
Object o = get(key);
if (o instanceof LazyString)
return o.toString();
if (o instanceof String)
return (String) o;
return default_;
@@ -252,7 +254,8 @@ public class JsonArray extends ArrayList<Object> {
* Returns true if the array has a string element at that index.
*/
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>> {
private Stack<Object> json = new Stack<>();
private String pendingKey;
private T root;
JsonBuilder(T root) {
@@ -73,12 +74,20 @@ public final class JsonBuilder<T> implements JsonSink<JsonBuilder<T>> {
@Override
public JsonBuilder<T> value(Object o) {
arr().add(o);
if (pendingKey != null) {
obj().put(pendingKey, o);
pendingKey = null;
} else {
arr().add(o);
}
return this;
}
@Override
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);
return this;
}
@@ -193,6 +202,18 @@ public final class JsonBuilder<T> implements JsonSink<JsonBuilder<T>> {
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() {
try {
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;
import java.math.BigDecimal;
import ch.randelshofer.fastdoubleparser.JavaBigDecimalParser;
import ch.randelshofer.fastdoubleparser.JavaDoubleParser;
import ch.randelshofer.fastdoubleparser.JavaFloatParser;
/**
* Lazily-parsed number for performance.
*/
@SuppressWarnings("serial")
class JsonLazyNumber extends Number {
private String value;
private char[] value;
private boolean isDouble;
JsonLazyNumber(String number, boolean isDoubleValue) {
JsonLazyNumber(char[] number, boolean isDoubleValue) {
this.value = number;
this.isDouble = isDoubleValue;
}
@Override
public double doubleValue() {
return Double.parseDouble(value);
return JavaDoubleParser.parseDouble(value);
}
@Override
public float floatValue() {
return Float.parseFloat(value);
return JavaFloatParser.parseFloat(value);
}
@Override
public int intValue() {
return isDouble ? (int)Double.parseDouble(value) : Integer.parseInt(value);
return isDouble ? (int)JavaDoubleParser.parseDouble(value) : Integer.parseInt(new String(value));
}
@Override
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}.
*/
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_) {
Object o = get(key);
if (o instanceof LazyString)
return o.toString();
if (o instanceof String)
return (String) o;
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.
*/
public boolean isString(String key) {
return get(key) instanceof String;
Object o = get(key);
return o instanceof LazyString || o instanceof String;
}
}

View File

@@ -15,16 +15,19 @@
*/
package com.grack.nanojson;
import ch.randelshofer.fastdoubleparser.JavaBigIntegerParser;
import ch.randelshofer.fastdoubleparser.JavaDoubleParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigInteger;
import java.net.URL;
import java.util.Arrays;
/**
* Simple JSON parser.
*
*
* <pre>
* Object json = {@link JsonParser}.any().from("{\"a\":[true,false], \"b\":1}");
* Number json = ({@link Number}){@link JsonParser}.any().from("123.456e7");
@@ -36,18 +39,21 @@ public final class JsonParser {
private Object value;
private int token;
private JsonTokener tokener;
private boolean lazyNumbers;
private final JsonTokener tokener;
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}.
*
* @param <T> The parsed type.
*/
public static final class JsonParserContext<T> {
private final Class<T> clazz;
private boolean lazyNumbers;
private boolean lazyNumbers = true;
private boolean lazyStrings = true;
JsonParserContext(Class<T> clazz) {
this.clazz = clazz;
@@ -62,18 +68,27 @@ public final class JsonParser {
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}.
*/
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}.
*/
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 {
try {
InputStream stm = url.openStream();
try {
try (InputStream stm = url.openStream()) {
return from(stm);
} finally {
stm.close();
}
} catch (IOException e) {
throw new JsonParserException(e, "IOException opening URL", 1, 1, 0);
@@ -93,21 +105,23 @@ 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 {
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.lazyNumbers = lazyNumbers;
this.lazyStrings = lazyStrings;
}
/**
* Parses a {@link JsonObject} from a source.
*
*
* <pre>
* JsonObject json = {@link JsonParser}.object().from("{\"a\":[true,false], \"b\":1}");
* </pre>
@@ -118,7 +132,7 @@ public final class JsonParser {
/**
* Parses a {@link JsonArray} from a source.
*
*
* <pre>
* JsonArray json = {@link JsonParser}.array().from("[1, {\"a\":[true,false], \"b\":1}]");
* </pre>
@@ -128,9 +142,11 @@ public final class JsonParser {
}
/**
* Parses any object from a source. For any valid JSON, returns either a null (for the JSON string 'null'), a
* {@link String}, a {@link Number}, a {@link Boolean}, a {@link JsonObject} or a {@link JsonArray}.
*
* Parses any object from a source. For any valid JSON, returns either a null
* (for the JSON string 'null'), a
* {@link String}, a {@link Number}, a {@link Boolean}, a {@link JsonObject} or
* a {@link JsonArray}.
*
* <pre>
* Object json = {@link JsonParser}.any().from("{\"a\":[true,false], \"b\":1}");
* Number json = ({@link Number}){@link JsonParser}.any().from("123.456e7");
@@ -144,138 +160,138 @@ public final class JsonParser {
* Parse a single JSON value from the string, expecting an EOF at the end.
*/
<T> T parse(Class<T> clazz) throws JsonParserException {
advanceToken(false, false);
Object parsed = currentValue();
if (advanceToken(false, false) != JsonTokener.TOKEN_EOF)
throw tokener.createParseException(null, "Expected end of input, got " + token, true);
if (clazz != Object.class && (parsed == null || !clazz.isAssignableFrom(parsed.getClass())))
throw tokener.createParseException(null,
"JSON did not contain the correct type, expected " + clazz.getSimpleName() + ".",
true);
return clazz.cast(parsed);
try {
advanceToken();
Object parsed = currentValue();
if (advanceToken() != JsonTokener.TOKEN_EOF)
throw tokener.createParseException(null, "Expected end of input, got " + token, true);
if (clazz != Object.class && (parsed == null || !clazz.isAssignableFrom(parsed.getClass())))
throw tokener.createParseException(null,
"JSON did not contain the correct type, expected " + clazz.getSimpleName() + ".",
true);
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.
*/
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)
return value;
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.
*/
private int advanceToken(boolean allowSemiString, boolean old) throws JsonParserException {
if (old) tokener.index--;
token = tokener.advanceToToken(allowSemiString);
private int advanceToken() throws JsonParserException {
token = tokener.advanceToToken();
switch (token) {
case JsonTokener.TOKEN_ARRAY_START: // Inlined function to avoid additional stack
JsonArray list = new JsonArray();
if (advanceToken(false, false) != JsonTokener.TOKEN_ARRAY_END)
while (true) {
list.add(currentValue());
if (token == JsonTokener.TOKEN_SEMI_STRING)
throw tokener.createParseException(null, "Semi-string is not allowed in array", true);
if (advanceToken(false, false) == JsonTokener.TOKEN_ARRAY_END)
break;
if (token != JsonTokener.TOKEN_COMMA)
throw tokener.createParseException(null,
"Expected a comma or end of the array instead of " + token, true);
if (advanceToken(false, false) == JsonTokener.TOKEN_ARRAY_END)
throw tokener.createParseException(null, "Trailing comma found in array", true);
}
value = list;
return token = JsonTokener.TOKEN_ARRAY_START;
case JsonTokener.TOKEN_OBJECT_START: // Inlined function to avoid additional stack
JsonObject map = new JsonObject();
if (advanceToken(true, false) != JsonTokener.TOKEN_OBJECT_END)
while (true) {
switch (token) {
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);
case JsonTokener.TOKEN_ARRAY_START: // Inlined function to avoid additional stack
JsonArray list = new JsonArray();
if (advanceToken() != JsonTokener.TOKEN_ARRAY_END)
while (true) {
list.add(currentValue());
if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
break;
if (token != JsonTokener.TOKEN_COMMA)
throw tokener.createParseException(null,
"Expected a comma or end of the array instead of " + token, true);
if (advanceToken() == JsonTokener.TOKEN_ARRAY_END)
throw tokener.createParseException(null, "Trailing comma found in array", true);
}
String key = (String)value;
if (token == JsonTokener.TOKEN_SEMI_STRING) {
if (advanceToken(false, true) != JsonTokener.TOKEN_COLON)
value = list;
return token = JsonTokener.TOKEN_ARRAY_START;
case JsonTokener.TOKEN_OBJECT_START: // Inlined function to avoid additional stack
JsonObject map = new JsonObject();
if (advanceToken() != JsonTokener.TOKEN_OBJECT_END)
while (true) {
if (token != JsonTokener.TOKEN_STRING)
throw tokener.createParseException(null, "Expected STRING, got " + token, true);
String key = lazyStrings ? value.toString() : (String) value;
if (advanceToken() != JsonTokener.TOKEN_COLON)
throw tokener.createParseException(null, "Expected COLON, got " + token, true);
} else if (advanceToken(false, false) != JsonTokener.TOKEN_COLON)
throw tokener.createParseException(null, "Expected COLON, got " + token, true);
advanceToken(false, false);
map.put(key, currentValue());
if (advanceToken(false, false) == JsonTokener.TOKEN_OBJECT_END)
break;
if (token != JsonTokener.TOKEN_COMMA)
throw tokener.createParseException(null,
"Expected a comma or end of the object instead of " + token, true);
if (advanceToken(true, false) == JsonTokener.TOKEN_OBJECT_END)
throw tokener.createParseException(null, "Trailing object found in array", true);
advanceToken();
map.put(key, currentValue());
if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
break;
if (token != JsonTokener.TOKEN_COMMA)
throw tokener.createParseException(null,
"Expected a comma or end of the object instead of " + token, true);
if (advanceToken() == JsonTokener.TOKEN_OBJECT_END)
throw tokener.createParseException(null, "Trailing object found in array", true);
}
value = map;
return token = JsonTokener.TOKEN_OBJECT_START;
case JsonTokener.TOKEN_TRUE:
value = Boolean.TRUE;
break;
case JsonTokener.TOKEN_FALSE:
value = Boolean.FALSE;
break;
case JsonTokener.TOKEN_NULL:
value = null;
break;
case JsonTokener.TOKEN_STRING:
char[] chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
value = lazyStrings ? new LazyString(chars) : new String(chars);
break;
case JsonTokener.TOKEN_NUMBER:
if (lazyNumbers) {
chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
value = new JsonLazyNumber(chars, tokener.isDouble);
} else {
value = parseNumber();
}
value = map;
return token = JsonTokener.TOKEN_OBJECT_START;
case JsonTokener.TOKEN_TRUE:
value = Boolean.TRUE;
break;
case JsonTokener.TOKEN_FALSE:
value = Boolean.FALSE;
break;
case JsonTokener.TOKEN_NULL:
value = null;
break;
case JsonTokener.TOKEN_STRING:
case JsonTokener.TOKEN_SEMI_STRING:
value = tokener.reusableBuffer.toString();
break;
case JsonTokener.TOKEN_NUMBER:
if (lazyNumbers) {
value = new JsonLazyNumber(tokener.reusableBuffer.toString(), tokener.isDouble);
} else {
value = parseNumber();
}
break;
default:
break;
default:
}
return token;
}
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 {
if (tokener.isDouble)
return Double.parseDouble(number);
return JavaDoubleParser.parseDouble(number);
// Quick parse for single-digits
if (number.length() == 1) {
return number.charAt(0) - '0';
} else if (number.length() == 2 && number.charAt(0) == '-') {
return '0' - number.charAt(1);
if (numLength == 1) {
return number[0] - '0';
} else if (numLength == 2 && number[0] == '-') {
return '0' - number[1];
}
// HACK: Attempt to parse using the approximate best type for this
boolean firstMinus = number.charAt(0) == '-';
int length = firstMinus ? number.length() - 1 : number.length();
boolean firstMinus = number[0] == '-';
int length = firstMinus ? numLength - 1 : numLength;
// CHECKSTYLE_OFF: MagicNumber
if (length < 10 || (length == 10 && number.charAt(firstMinus ? 1 : 0) < '2')) // 2 147 483 647
return Integer.parseInt(number);
if (length < 19 || (length == 19 && number.charAt(firstMinus ? 1 : 0) < '9')) // 9 223 372 036 854 775 807
return Long.parseLong(number);
if (length < 10 || (length == 10 && number[firstMinus ? 1 : 0] < '2')) // 2 147 483 647
return Integer.parseInt(new String(number));
if (length < 19 || (length == 19 && number[firstMinus ? 1 : 0] < '9')) // 9 223 372 036 854 775 807
return Long.parseLong(new String(number));
// CHECKSTYLE_ON: MagicNumber
return new BigInteger(number);
return JavaBigIntegerParser.parseBigInteger(number);
} 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;
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.Reader;
import java.io.StringReader;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.BitSet;
/**
* Streaming reader for JSON documents.
*/
public final class JsonReader {
public final class JsonReader implements Closeable {
private JsonTokener tokener;
private int token;
private BitSet states = new BitSet();
private int stateIndex = 0;
private boolean inObject;
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.
@@ -76,12 +85,19 @@ public final class JsonReader {
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.
*/
JsonReader(JsonTokener tokener) throws JsonParserException {
this.tokener = tokener;
token = tokener.advanceToToken(false);
token = tokener.advanceToToken();
}
/**
@@ -90,7 +106,8 @@ public final class JsonReader {
*/
public boolean pop() throws JsonParserException {
// CHECKSTYLE_OFF: EmptyStatement
while (!next());
while (!next())
;
// CHECKSTYLE_ON: EmptyStatement
first = false;
inObject = states.get(--stateIndex);
@@ -102,23 +119,23 @@ public final class JsonReader {
*/
public Type current() throws JsonParserException {
switch (token) {
case JsonTokener.TOKEN_TRUE:
case JsonTokener.TOKEN_FALSE:
return Type.BOOLEAN;
case JsonTokener.TOKEN_NULL:
return Type.NULL;
case JsonTokener.TOKEN_NUMBER:
return Type.NUMBER;
case JsonTokener.TOKEN_STRING:
return Type.STRING;
case JsonTokener.TOKEN_OBJECT_START:
return Type.OBJECT;
case JsonTokener.TOKEN_ARRAY_START:
return Type.ARRAY;
default:
throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_TRUE,
JsonTokener.TOKEN_FALSE, JsonTokener.TOKEN_NUMBER, JsonTokener.TOKEN_STRING,
JsonTokener.TOKEN_OBJECT_START, JsonTokener.TOKEN_ARRAY_START);
case JsonTokener.TOKEN_TRUE:
case JsonTokener.TOKEN_FALSE:
return Type.BOOLEAN;
case JsonTokener.TOKEN_NULL:
return Type.NULL;
case JsonTokener.TOKEN_NUMBER:
return Type.NUMBER;
case JsonTokener.TOKEN_STRING:
return Type.STRING;
case JsonTokener.TOKEN_OBJECT_START:
return Type.OBJECT;
case JsonTokener.TOKEN_ARRAY_START:
return Type.ARRAY;
default:
throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_TRUE,
JsonTokener.TOKEN_FALSE, JsonTokener.TOKEN_NUMBER, JsonTokener.TOKEN_STRING,
JsonTokener.TOKEN_OBJECT_START, JsonTokener.TOKEN_ARRAY_START);
}
}
@@ -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 {
if (!inObject)
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);
}
/**
@@ -158,19 +178,20 @@ public final class JsonReader {
*/
public Object value() throws JsonParserException {
switch (token) {
case JsonTokener.TOKEN_TRUE:
return true;
case JsonTokener.TOKEN_FALSE:
return false;
case JsonTokener.TOKEN_NULL:
return null;
case JsonTokener.TOKEN_NUMBER:
return number();
case JsonTokener.TOKEN_STRING:
return string();
default:
throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_TRUE, JsonTokener.TOKEN_FALSE,
JsonTokener.TOKEN_NUMBER, JsonTokener.TOKEN_STRING);
case JsonTokener.TOKEN_TRUE:
return true;
case JsonTokener.TOKEN_FALSE:
return false;
case JsonTokener.TOKEN_NULL:
return null;
case JsonTokener.TOKEN_NUMBER:
return number();
case JsonTokener.TOKEN_STRING:
return string();
default:
throw createTokenMismatchException(JsonTokener.TOKEN_NULL, JsonTokener.TOKEN_TRUE,
JsonTokener.TOKEN_FALSE,
JsonTokener.TOKEN_NUMBER, JsonTokener.TOKEN_STRING);
}
}
@@ -190,7 +211,9 @@ public final class JsonReader {
return null;
if (token != 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,55 +234,61 @@ public final class JsonReader {
public Number number() throws JsonParserException {
if (token == JsonTokener.TOKEN_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.
*/
public long longVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString();
return tokener.isDouble ? (long)Double.parseDouble(s) : Long.parseLong(s);
char[] chars = tokener.reusableBuffer.array();
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.
*/
public int intVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString();
return tokener.isDouble ? (int)Double.parseDouble(s) : Integer.parseInt(s);
char[] chars = tokener.reusableBuffer.array();
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.
*/
public float floatVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString();
return Float.parseFloat(s);
char[] chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return JavaFloatParser.parseFloat(chars);
}
/**
* Parses the current value as a double.
*/
public double doubleVal() throws JsonParserException {
String s = tokener.reusableBuffer.toString();
return Double.parseDouble(s);
char[] chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
return JavaDoubleParser.parseDouble(chars);
}
/**
* Advance to the next value in this array or object. If no values remain,
* return to the parent array or object.
*
*
* @return true if we still have values to read in this array or object,
* false if we have completed this object (and implicitly moved back
* to the parent array or object)
*/
public boolean next() throws JsonParserException {
if (stateIndex == 0) {
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 (token == JsonTokener.TOKEN_OBJECT_END) {
@@ -267,20 +296,22 @@ public final class JsonReader {
first = false;
return false;
}
if (!first) {
if (token != JsonTokener.TOKEN_COMMA)
throw createTokenMismatchException(JsonTokener.TOKEN_COMMA, JsonTokener.TOKEN_OBJECT_END);
token = tokener.advanceToToken(false);
token = tokener.advanceToToken();
}
if (token != JsonTokener.TOKEN_STRING)
throw createTokenMismatchException(JsonTokener.TOKEN_STRING);
key.setLength(0);
key.append(tokener.reusableBuffer); // reduce string garbage
if ((token = tokener.advanceToToken(false)) != JsonTokener.TOKEN_COLON)
key.clear();
char[] chars = tokener.reusableBuffer.array();
chars = Arrays.copyOf(chars, tokener.reusableBuffer.position());
key.put(chars);
if ((token = tokener.advanceToToken()) != JsonTokener.TOKEN_COLON)
throw createTokenMismatchException(JsonTokener.TOKEN_COLON);
token = tokener.advanceToToken(false);
token = tokener.advanceToToken();
} else {
if (token == JsonTokener.TOKEN_ARRAY_END) {
inObject = states.get(--stateIndex);
@@ -290,7 +321,7 @@ public final class JsonReader {
if (!first) {
if (token != JsonTokener.TOKEN_COMMA)
throw createTokenMismatchException(JsonTokener.TOKEN_COMMA, JsonTokener.TOKEN_ARRAY_END);
token = tokener.advanceToToken(false);
token = tokener.advanceToToken();
}
}
@@ -303,13 +334,28 @@ public final class JsonReader {
JsonTokener.TOKEN_OBJECT_START, JsonTokener.TOKEN_ARRAY_START);
first = false;
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) {
return tokener.createParseException(null, "token mismatch (expected " + Arrays.toString(t)
+ ", was " + token + ")",
+ ", was " + token + ")",
true);
}
}

View File

@@ -159,4 +159,9 @@ public interface JsonSink<SELF extends JsonSink<SELF>> {
* Ends the current array or object.
*/
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.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
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
static final int BUFFER_SIZE = 32 * 1024;
static final int MAX_CHAR_BUFFER_SIZE = 512;
static final int BUFFER_ROOM = 256;
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 boolean eof;
protected int index;
private int index;
private final Reader reader;
private final char[] buffer = new char[BUFFER_SIZE];
private int bufferLength;
private final boolean utf8;
protected StringBuilder reusableBuffer = new StringBuilder();
protected CharBuffer reusableBuffer = CharBufferPool.get(MAX_CHAR_BUFFER_SIZE);
protected boolean isDouble;
static final char[] TRUE = { 'r', 'u', 'e' };
@@ -64,11 +69,11 @@ final class JsonTokener {
static final int TOKEN_NUMBER = 9;
static final int TOKEN_OBJECT_START = 10;
static final int TOKEN_ARRAY_START = 11;
static final int TOKEN_SEMI_STRING = 12;
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 final InputStream buffered;
@@ -82,7 +87,7 @@ final class JsonTokener {
public int read(char[] cbuf, int off, int len) throws IOException {
int r = buffered.read(buf, off, len);
for (int i = off; i < off + r; i++)
cbuf[i] = (char)buf[i];
cbuf[i] = (char) buf[i];
return r;
}
@@ -90,15 +95,15 @@ final class JsonTokener {
public void close() throws IOException {
}
}
JsonTokener(Reader reader) throws JsonParserException {
this.reader = reader;
this.utf8 = false;
init();
}
JsonTokener(InputStream stm) throws JsonParserException {
final InputStream buffered = (stm instanceof BufferedInputStream || stm instanceof ByteArrayInputStream)
final InputStream buffered = (stm instanceof BufferedInputStream || stm instanceof ByteArrayInputStream)
? stm
: new BufferedInputStream(stm);
buffered.mark(4);
@@ -176,30 +181,17 @@ final class JsonTokener {
fixupAfterRawBufferRead();
// The token shouldn't end with something other than an ASCII letter
switch (peekChar()) {
case ',':
case ':':
case '{':
case '}':
case '[':
case ']':
case ' ':
case '\n':
case '\r':
case '\t':
break;
default:
// The token should end with something other than an ASCII letter
if (isAsciiLetter(peekChar()))
throw createHelpfulException(first, expected, expected.length);
}
}
/**
* Steps through to the end of the current number token (a non-digit token).
*/
void consumeTokenNumber(char savedChar) throws JsonParserException {
reusableBuffer.setLength(0);
reusableBuffer.append(savedChar);
reusableBuffer.clear();
reusableBuffer.put(savedChar);
isDouble = false;
// The JSON spec is way stricter about number formats than
@@ -213,9 +205,8 @@ final class JsonTokener {
} else {
state = 2;
}
outer:
while (true) {
outer: while (true) {
int n = ensureBuffer(BUFFER_ROOM);
if (n == 0)
break outer;
@@ -226,296 +217,176 @@ final class JsonTokener {
break outer;
int ns = -1;
sw:
switch (state) {
case 1: // start leading negative
if (nc == '0') {
ns = 3; break sw;
}
if (nc > '0' && nc <= '9') {
ns = 2; break sw;
}
break;
case 2: // no leading zero
case 3: // leading zero
if ((nc >= '0' && nc <= '9') && state == 2) {
ns = 2; break sw;
}
if (nc == '.') {
isDouble = true;
ns = 4; break sw;
}
if (nc == 'e' || nc == 'E') {
isDouble = true;
ns = 6; break sw;
}
break;
case 4: // after period
case 5: // after period, one digit read
if (nc >= '0' && nc <= '9') {
ns = 5; break sw;
}
if ((nc == 'e' || nc == 'E') && state == 5) {
isDouble = true;
ns = 6; break sw;
}
break;
case 6: // after exponent
case 7: // after exponent and sign
if (nc == '+' || nc == '-' && state == 6) {
ns = 7; break sw;
}
if (nc >= '0' && nc <= '9') {
ns = 8; break sw;
}
break;
case 8: // after digits
if (nc >= '0' && nc <= '9') {
ns = 8; break sw;
}
break;
default:
assert false : "Impossible"; // will throw malformed number
sw: switch (state) {
case 1: // start leading negative
if (nc == '0') {
ns = 3;
break sw;
}
if (nc > '0' && nc <= '9') {
ns = 2;
break sw;
}
break;
case 2: // no leading zero
case 3: // leading zero
if ((nc >= '0' && nc <= '9') && state == 2) {
ns = 2;
break sw;
}
if (nc == '.') {
isDouble = true;
ns = 4;
break sw;
}
if (nc == 'e' || nc == 'E') {
isDouble = true;
ns = 6;
break sw;
}
break;
case 4: // after period
case 5: // after period, one digit read
if (nc >= '0' && nc <= '9') {
ns = 5;
break sw;
}
if ((nc == 'e' || nc == 'E') && state == 5) {
isDouble = true;
ns = 6;
break sw;
}
break;
case 6: // after exponent
case 7: // after exponent and sign
if (nc == '+' || nc == '-' && state == 6) {
ns = 7;
break sw;
}
if (nc >= '0' && nc <= '9') {
ns = 8;
break sw;
}
break;
case 8: // after digits
if (nc >= '0' && nc <= '9') {
ns = 8;
break sw;
}
break;
default:
assert false : "Impossible"; // will throw malformed number
}
reusableBuffer.append(nc);
reusableBuffer.put(nc);
index++;
if (ns == -1)
throw createParseException(null, "Malformed number: " + reusableBuffer, true);
state = ns;
}
}
if (state != 2 && state != 3 && state != 5 && state != 8)
throw createParseException(null, "Malformed number: " + reusableBuffer, true);
// Special case for -0
if (state == 3 && savedChar == '-')
isDouble = true;
fixupAfterRawBufferRead();
}
/**
* 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 {
reusableBuffer.setLength(0);
void consumeTokenString() throws JsonParserException {
reusableBuffer.position(0);
// Assume no escapes or UTF-8 in the string to start (fast path)
start:
while (true) {
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 (c == cc) {
if (c == '"') {
// Use the index before we fixup
reusableBuffer.append(buffer, index - i - 1, i);
expandBufferIfNeeded(i);
reusableBuffer.put(buffer, index - i - 1, i);
fixupAfterRawBufferRead();
return;
}
if (c == '\\' || (utf8 && (c & 0x80) != 0)) {
reusableBuffer.append(buffer, index - i - 1, i);
expandBufferIfNeeded(i);
reusableBuffer.put(buffer, index - i - 1, i);
index--;
break start;
}
}
reusableBuffer.append(buffer, index - n, n);
expandBufferIfNeeded(n);
reusableBuffer.put(buffer, index - n, n);
}
outer:
while (true) {
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) {
// 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();
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 '\'':
if (c == cc) {
case '\"':
fixupAfterRawBufferRead();
return;
} else {
reusableBuffer.append(c);
break;
}
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 '/':
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);
// 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);
}
}
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);
}
}
}
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');
char escape = buffer[index++];
switch (escape) {
case 'b':
reusableBuffer.put('\b');
break;
case 'f':
reusableBuffer.put('\f');
break;
case 'n':
reusableBuffer.put('\n');
break;
case 'r':
reusableBuffer.put('\r');
break;
case 't':
reusableBuffer.put('\t');
break;
case '"':
case '/':
case '\\':
reusableBuffer.append(escape);
reusableBuffer.put(escape);
break;
case 'u':
int escaped = 0;
@@ -530,25 +401,28 @@ final class JsonTokener {
} else if (digit >= 'a' && digit <= 'f') {
escaped |= (digit - 'a') + 10;
} else {
throw createParseException(null, "Expected unicode hex escape character: "
+ (char)digit + " (" + digit + ")", false);
throw createParseException(null,
"Expected unicode hex escape character: "
+ (char) digit + " (" + digit + ")", false);
}
}
reusableBuffer.append((char)escaped);
reusableBuffer.put((char) escaped);
break;
default:
throw createParseException(null, "Invalid escape: \\" + escape, false);
}
break;
default:
reusableBuffer.append(c);
reusableBuffer.put(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);
throw createParseException(null,
"EOF encountered in the middle of a string escape",
false);
}
}
}
@@ -557,75 +431,82 @@ final class JsonTokener {
private void consumeTokenStringUtf8Char(char c) throws JsonParserException {
ensureBuffer(5);
// Worst case (supplementary plane) decodes to a surrogate pair (2 chars)
expandBufferIfNeeded(2);
// Hand-UTF8-decoding
switch (c & 0xf0) {
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
throw createParseException(null,
"Illegal UTF-8 continuation byte: 0x" + Integer.toHexString(c & 0xff), false);
case 0xc0:
// Check for illegal C0 and C1 bytes
if ((c & 0xe) == 0)
throw createParseException(null, "Illegal UTF-8 byte: 0x" + Integer.toHexString(c & 0xff),
false);
// fall-through
case 0xd0:
c = (char)((c & 0x1f) << 6 | (buffer[index++] & 0x3f));
reusableBuffer.append(c);
utf8adjust++;
break;
case 0xe0:
c = (char)((c & 0x0f) << 12 | (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f));
utf8adjust += 2;
// Check for illegally-encoded surrogate - http://unicode.org/faq/utf_bom.html#utf8-4
if ((c >= '\ud800' && c <= '\udbff') || (c >= '\udc00' && c <= '\udfff'))
throw createParseException(null, "Illegal UTF-8 codepoint: 0x" + Integer.toHexString(c),
false);
reusableBuffer.append(c);
break;
case 0xf0:
if ((c & 0xf) >= 5)
throw createParseException(null, "Illegal UTF-8 byte: 0x" + Integer.toHexString(c & 0xff),
false);
// Extended char
switch ((c & 0xc) >> 2) {
case 0:
case 1:
reusableBuffer.appendCodePoint((c & 7) << 18 | (buffer[index++] & 0x3f) << 12
| (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f));
utf8adjust += 3;
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
throw createParseException(null,
"Illegal UTF-8 continuation byte: 0x" + Integer.toHexString(c & 0xff), false);
case 0xc0:
// Check for illegal C0 and C1 bytes
if ((c & 0xe) == 0)
throw createParseException(null, "Illegal UTF-8 byte: 0x" + Integer.toHexString(c & 0xff),
false);
// fall-through
case 0xd0:
c = (char) ((c & 0x1f) << 6 | (buffer[index++] & 0x3f));
reusableBuffer.put(c);
utf8adjust++;
break;
case 0xe0:
c = (char) ((c & 0x0f) << 12 | (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f));
utf8adjust += 2;
// Check for illegally-encoded surrogate -
// http://unicode.org/faq/utf_bom.html#utf8-4
if ((c >= '\ud800' && c <= '\udbff') || (c >= '\udc00' && c <= '\udfff'))
throw createParseException(null, "Illegal UTF-8 codepoint: 0x" + Integer.toHexString(c),
false);
reusableBuffer.put(c);
break;
case 0xf0:
if ((c & 0xf) >= 5)
throw createParseException(null, "Illegal UTF-8 byte: 0x" + Integer.toHexString(c & 0xff),
false);
// Extended char
switch ((c & 0xc) >> 2) {
case 0:
case 1:
reusableBuffer.put(Character.toChars((c & 7) << 18 | (buffer[index++] & 0x3f) << 12
| (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f)));
utf8adjust += 3;
break;
case 2:
// TODO: \uFFFD (replacement char)
int codepoint = (c & 3) << 24 | (buffer[index++] & 0x3f) << 18 | (buffer[index++] & 0x3f) << 12
| (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f);
throw createParseException(null,
"Unable to represent codepoint 0x" + Integer.toHexString(codepoint)
+ " in a Java string",
false);
case 3:
codepoint = (c & 1) << 30 | (buffer[index++] & 0x3f) << 24 | (buffer[index++] & 0x3f) << 18
| (buffer[index++] & 0x3f) << 12 | (buffer[index++] & 0x3f) << 6
| (buffer[index++] & 0x3f);
throw createParseException(null,
"Unable to represent codepoint 0x" + Integer.toHexString(codepoint)
+ " in a Java string",
false);
default:
assert false : "Impossible";
}
break;
case 2:
// TODO: \uFFFD (replacement char)
int codepoint = (c & 3) << 24 | (buffer[index++] & 0x3f) << 18 | (buffer[index++] & 0x3f) << 12
| (buffer[index++] & 0x3f) << 6 | (buffer[index++] & 0x3f);
throw createParseException(null,
"Unable to represent codepoint 0x" + Integer.toHexString(codepoint)
+ " in a Java string", false);
case 3:
codepoint = (c & 1) << 30 | (buffer[index++] & 0x3f) << 24 | (buffer[index++] & 0x3f) << 18
| (buffer[index++] & 0x3f) << 12 | (buffer[index++] & 0x3f) << 6
| (buffer[index++] & 0x3f);
throw createParseException(null,
"Unable to represent codepoint 0x" + Integer.toHexString(codepoint)
+ " in a Java string", false);
default:
assert false : "Impossible";
}
break;
default:
// Regular old byte
break;
// Regular old byte
break;
}
if (index > bufferLength)
throw createParseException(null, "UTF-8 codepoint was truncated", false);
}
/**
* 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 {
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 {
// We're good here
@@ -700,7 +582,8 @@ final class JsonTokener {
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) {
charOffset += index;
bufferLength = bufferLength - index;
@@ -748,7 +631,7 @@ final class JsonTokener {
return c;
}
int advanceCharFast() {
int c = buffer[index];
if (c == '\n') {
@@ -760,7 +643,7 @@ final class JsonTokener {
index++;
return c;
}
private void consumeWhitespace() throws JsonParserException {
int n;
do {
@@ -781,12 +664,13 @@ final class JsonTokener {
} while (n > 0);
eof = 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.
*/
int advanceToToken(boolean allowSemiString) throws JsonParserException {
int advanceToToken() throws JsonParserException {
int c = advanceChar();
while (isWhitespace(c))
c = advanceChar();
@@ -794,102 +678,69 @@ final class JsonTokener {
tokenCharPos = index + charOffset - rowPos - utf8adjust;
tokenCharOffset = charOffset + index;
int oldIndex = index;
int token;
switch (c) {
case -1:
return TOKEN_EOF;
case '[':
token = TOKEN_ARRAY_START;
break;
case ']':
token = TOKEN_ARRAY_END;
break;
case ',':
token = TOKEN_COMMA;
break;
case ':':
token = TOKEN_COLON;
break;
case '{':
token = TOKEN_OBJECT_START;
break;
case '}':
token = TOKEN_OBJECT_END;
break;
case 't':
try {
case -1:
return TOKEN_EOF;
case '[':
token = TOKEN_ARRAY_START;
break;
case ']':
token = TOKEN_ARRAY_END;
break;
case ',':
token = TOKEN_COMMA;
break;
case ':':
token = TOKEN_COLON;
break;
case '{':
token = TOKEN_OBJECT_START;
break;
case '}':
token = TOKEN_OBJECT_END;
break;
case 't':
consumeKeyword((char) c, JsonTokener.TRUE);
token = TOKEN_TRUE;
} catch (JsonParserException e) {
if (allowSemiString) {
index = oldIndex - 1;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
} else throw e;
}
break;
case 'f':
try {
consumeKeyword((char)c, JsonTokener.FALSE);
token = TOKEN_FALSE;
} catch (JsonParserException e) {
if (allowSemiString) {
index = oldIndex - 1;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
} else throw e;
}
break;
case 'n':
try {
consumeKeyword((char)c, JsonTokener.NULL);
token = TOKEN_NULL;
} catch (JsonParserException e) {
if (allowSemiString) {
index = oldIndex - 1;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
} else throw e;
}
break;
case '"':
case '\'':
consumeTokenString(c);
token = TOKEN_STRING;
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
consumeTokenNumber((char)c);
token = TOKEN_NUMBER;
break;
case '+':
case '.':
throw createParseException(null, "Numbers may not start with '" + (char)c + "'", true);
default:
if (allowSemiString) {
index--;
consumeTokenSemiString();
token = TOKEN_SEMI_STRING;
break;
} else {
case 'f':
consumeKeyword((char) c, JsonTokener.FALSE);
token = TOKEN_FALSE;
break;
case 'n':
consumeKeyword((char) c, JsonTokener.NULL);
token = TOKEN_NULL;
break;
case '\"':
consumeTokenString();
token = TOKEN_STRING;
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
consumeTokenNumber((char) c);
token = TOKEN_NUMBER;
break;
case '+':
case '.':
throw createParseException(null, "Numbers may not start with '" + (char) c + "'", true);
default:
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;
}
@@ -908,6 +759,18 @@ final class JsonTokener {
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.
*/
@@ -919,14 +782,30 @@ final class JsonTokener {
// Consume the whole pseudo-token to make a better error message
while (isAsciiLetter(peekChar()) && errorToken.length() < 15)
errorToken.append((char)advanceChar());
errorToken.append((char) advanceChar());
return createParseException(null, "Unexpected token '" + errorToken + "'"
+ (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) {
if (tokenPos)

View File

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

View File

@@ -26,9 +26,9 @@ import java.util.Map;
* Internal class that handles emitting to an {@link Appendable}. Users only see
* the public subclasses, {@link JsonStringWriter} and
* {@link JsonAppendableWriter}.
*
*
* @param <SELF>
* A subclass of {@link JsonWriterBase}.
* A subclass of {@link JsonWriterBase}.
*/
class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
JsonSink<SELF> {
@@ -50,6 +50,7 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
private int stateIndex = 0;
private boolean first = true;
private boolean inObject;
private String pendingKey;
/**
* Sequence to use for indenting.
@@ -123,8 +124,9 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
Object o = entry.getValue();
if (!(entry.getKey() instanceof String))
throw new JsonWriterException("Invalid key type for map: "
+ (entry.getKey() == null ? "null" : entry.getKey()
.getClass()));
+ (entry.getKey() == null ? "null"
: entry.getKey()
.getClass()));
String k = (String) entry.getKey();
value(k, o);
}
@@ -152,6 +154,8 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
return nul();
else if (o instanceof String)
return value((String) o);
else if (o instanceof LazyString)
return value(o.toString());
else if (o instanceof Number)
return value(((Number) o));
else if (o instanceof Boolean)
@@ -166,7 +170,9 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
for (int i = 0; i < length; i++)
value(Array.get(o, i));
return end();
} else
} else if (o instanceof JsonConvertible)
return value(((JsonConvertible) o).toJsonValue());
else
throw new JsonWriterException("Unable to handle type: "
+ o.getClass());
}
@@ -177,6 +183,8 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
return nul(key);
else if (o instanceof String)
return value(key, (String) o);
else if (o instanceof LazyString)
return value(key, o.toString());
else if (o instanceof Number)
return value(key, (Number) o);
else if (o instanceof Boolean)
@@ -191,7 +199,9 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
for (int i = 0; i < length; i++)
value(Array.get(o, i));
return end();
} else
} else if (o instanceof JsonConvertible)
return value(key, ((JsonConvertible) o).toJsonValue());
else
throw new JsonWriterException("Unable to handle type: "
+ o.getClass());
}
@@ -243,7 +253,7 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
@Override
public SELF value(Number n) {
preValue();
if (n == null)
if (n == null || nullish(n))
raw(NULL);
else
raw(n.toString());
@@ -372,12 +382,25 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
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.
*
*
* @throws JsonWriterException
* if the written JSON is not properly balanced, ie: all arrays
* and objects that were started have been properly ended.
* if the written JSON is not properly balanced, ie:
* all arrays
* and objects that were started have been properly
* ended.
*/
protected void doneInternal() {
if (stateIndex > 0)
@@ -434,7 +457,7 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
if (utf8) {
if (bo + 1 > BUFFER_SIZE)
flush();
bb[bo++] = (byte)c;
bb[bo++] = (byte) c;
} else {
buffer.append(c);
if (buffer.length() > BUFFER_SIZE) {
@@ -472,6 +495,12 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
}
private void preValue() {
if (pendingKey != null) {
String key = pendingKey;
pendingKey = null;
preValue(key);
return;
}
if (inObject)
throw new JsonWriterException(
"Invalid call to emit a keyless value while writing an object");
@@ -483,6 +512,9 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
if (!inObject)
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");
pre();
@@ -505,82 +537,106 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
c = s.charAt(i);
switch (c) {
case '\\':
case '"':
raw('\\');
raw(c);
break;
case '/':
// Special case to ensure that </script> doesn't appear in JSON
// output
if (b == '<')
case '\\':
case '"':
raw('\\');
raw(c);
break;
case '\b':
raw("\\b");
break;
case '\t':
raw("\\t");
break;
case '\n':
raw("\\n");
break;
case '\f':
raw("\\f");
break;
case '\r':
raw("\\r");
break;
default:
if (shouldBeEscaped(c)) {
if (c < 0x100) {
raw(UNICODE_SMALL);
raw(HEX[(c >> 4) & 0xf]);
raw(HEX[c & 0xf]);
} else {
raw(UNICODE_LARGE);
raw(HEX[(c >> 12) & 0xf]);
raw(HEX[(c >> 8) & 0xf]);
raw(HEX[(c >> 4) & 0xf]);
raw(HEX[c & 0xf]);
}
} else {
if (utf8) {
if (bo + 4 > BUFFER_SIZE) // 4 is the max char size
flush();
if (c < 0x80) {
bb[bo++] = (byte) c;
} else if (c < 0x800) {
bb[bo++] = (byte) (0xc0 | c >> 6);
bb[bo++] = (byte) (0x80 | c & 0x3f);
} else if (c < 0xd800) {
bb[bo++] = (byte) (0xe0 | c >> 12);
bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | c & 0x3f);
} else if (c < 0xdfff) {
// TODO: bad surrogates
i++;
int fc = Character.toCodePoint(c, s.charAt(i));
if (fc < 0x1fffff) {
bb[bo++] = (byte) (0xf0 | fc >> 18);
bb[bo++] = (byte) (0x80 | (fc >> 12) & 0x3f);
bb[bo++] = (byte) (0x80 | (fc >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | fc & 0x3f);
} else {
throw new JsonWriterException("Unable to encode character 0x"
+ Integer.toHexString(fc));
}
raw(c);
break;
case '/':
// Special case to ensure that </script> doesn't appear in JSON
// output
if (b == '<')
raw('\\');
raw(c);
break;
case '\b':
raw("\\b");
break;
case '\t':
raw("\\t");
break;
case '\n':
raw("\\n");
break;
case '\f':
raw("\\f");
break;
case '\r':
raw("\\r");
break;
default:
if (shouldBeEscaped(c)) {
if (c < 0x100) {
raw(UNICODE_SMALL);
raw(HEX[(c >> 4) & 0xf]);
raw(HEX[c & 0xf]);
} else {
bb[bo++] = (byte) (0xe0 | c >> 12);
bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | c & 0x3f);
raw(UNICODE_LARGE);
raw(HEX[(c >> 12) & 0xf]);
raw(HEX[(c >> 8) & 0xf]);
raw(HEX[(c >> 4) & 0xf]);
raw(HEX[c & 0xf]);
}
} else {
raw(c);
if (utf8) {
// 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();
if (c < 0x80) {
bb[bo++] = (byte) c;
} else if (c < 0x800) {
bb[bo++] = (byte) (0xc0 | c >> 6);
bb[bo++] = (byte) (0x80 | c & 0x3f);
} else if (c < 0xd800) {
bb[bo++] = (byte) (0xe0 | c >> 12);
bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | c & 0x3f);
} else if (Character.isHighSurrogate(c)) {
// Surrogate pair handling (supplementary plane character)
// We have a high surrogate; must be followed by a low surrogate to form a valid
// code point.
if (i + 1 >= s.length()) {
throw new JsonWriterException("Invalid high surrogate at end of string");
}
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 >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | fc & 0x3f);
} else {
throw new JsonWriterException(
"Unable to encode character 0x" + Integer.toHexString(fc));
}
} else if (Character.isLowSurrogate(c)) {
throw new JsonWriterException(
"Invalid low surrogate without preceding high surrogate");
} else {
bb[bo++] = (byte) (0xe0 | c >> 12);
bb[bo++] = (byte) (0x80 | (c >> 6) & 0x3f);
bb[bo++] = (byte) (0x80 | c & 0x3f);
}
} else {
raw(c);
}
}
}
}
}
@@ -594,4 +650,25 @@ class JsonWriterBase<SELF extends JsonWriterBase<SELF>> implements
return c < ' ' || (c >= '\u0080' && c < '\u00a0')
|| (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,111 +15,113 @@
*/
package com.grack.nanojson;
import static org.junit.Assert.assertEquals;
import org.junit.jupiter.api.Test;
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.
*/
public class JsonNumberTest {
// CHECKSTYLE_OFF: MagicNumber
// CHECKSTYLE_OFF: JavadocMethod
@Test
public void testBasicNumberRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[1, 1.0, 1.00]");
assertEquals(Integer.class, array.get(0).getClass());
assertEquals(Double.class, array.get(1).getClass());
assertEquals(Double.class, array.get(2).getClass());
}
class JsonNumberTest {
// CHECKSTYLE_OFF: MagicNumber
// CHECKSTYLE_OFF: JavadocMethod
@Test
void basicNumberRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[1, 1.0, 1.00]");
assertEquals(1, ((Number) array.get(0)).intValue());
assertEquals(1.0, ((Number) array.get(1)).doubleValue(), 0.0);
assertEquals(1.0, ((Number) array.get(2)).doubleValue(), 0.0);
}
@Test
public void testBasicNumberWrite() {
JsonArray array = JsonArray.from(1, 1.0, 1.0f);
assertEquals("[1,1.0,1.0]", JsonWriter.string().array(array).done());
}
@Test
void basicNumberWrite() {
JsonArray array = JsonArray.from(1, 1.0, 1.0f);
assertEquals("[1,1.0,1.0]", JsonWriter.string().array(array).done());
}
@Test
public void testLargeIntRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[-300000000,300000000]");
assertEquals(Integer.class, array.get(0).getClass());
assertEquals(-300000000, array.get(0));
assertEquals(Integer.class, array.get(1).getClass());
assertEquals(300000000, array.get(1));
}
@Test
void largeIntRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[-300000000,300000000]");
assertEquals(-300000000, ((Number) array.get(0)).intValue());
assertEquals(300000000, ((Number) array.get(1)).intValue());
}
@Test
public void testLargeIntWrite() {
JsonArray array = JsonArray.from(-300000000, 300000000);
assertEquals("[-300000000,300000000]", JsonWriter.string().array(array)
.done());
}
@Test
void largeIntWrite() {
JsonArray array = JsonArray.from(-300000000, 300000000);
assertEquals("[-300000000,300000000]", JsonWriter.string().array(array)
.done());
}
@Test
public void testLongRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[-3000000000,3000000000]");
assertEquals(Long.class, array.get(0).getClass());
assertEquals(-3000000000L, array.get(0));
assertEquals(Long.class, array.get(1).getClass());
assertEquals(3000000000L, array.get(1));
}
@Test
void longRead() throws JsonParserException {
JsonArray array = JsonParser.array().from("[-3000000000,3000000000]");
assertEquals(-3000000000L, ((Number) array.get(0)).longValue());
assertEquals(3000000000L, ((Number) array.get(1)).longValue());
}
@Test
public void testLongWrite() {
JsonArray array = JsonArray.from(1L, -3000000000L, 3000000000L);
assertEquals("[1,-3000000000,3000000000]",
JsonWriter.string().array(array).done());
}
@Test
void longWrite() {
JsonArray array = JsonArray.from(1L, -3000000000L, 3000000000L);
assertEquals("[1,-3000000000,3000000000]",
JsonWriter.string().array(array).done());
}
@Test
public void testBigIntRead() throws JsonParserException {
JsonArray array = JsonParser.array().from(
"[-30000000000000000000,30000000000000000000]");
assertEquals(BigInteger.class, array.get(0).getClass());
assertEquals(new BigInteger("-30000000000000000000"), array.get(0));
assertEquals(BigInteger.class, array.get(1).getClass());
assertEquals(new BigInteger("30000000000000000000"), array.get(1));
}
@Test
void bigIntRead() throws JsonParserException {
JsonArray array = JsonParser.array().from(
"[-30000000000000000000,30000000000000000000]");
// cast to ensure it's a number
assertEquals("-30000000000000000000", ((Number) array.get(0)).toString());
assertEquals("30000000000000000000", ((Number) array.get(1)).toString());
}
@Test
public void testBigIntWrite() {
JsonArray array = JsonArray.from(BigInteger.ONE, new BigInteger(
"-30000000000000000000"),
new BigInteger("30000000000000000000"));
assertEquals("[1,-30000000000000000000,30000000000000000000]",
JsonWriter.string().array(array).done());
}
@Test
void bigIntWrite() {
JsonArray array = JsonArray.from(BigInteger.ONE, new BigInteger(
"-30000000000000000000"),
new BigInteger("30000000000000000000"));
assertEquals("[1,-30000000000000000000,30000000000000000000]",
JsonWriter.string().array(array).done());
}
/**
* Tests a bug where longs were silently truncated to floats.
*/
@Test
public void testLongBuilder() {
JsonObject o = JsonObject.builder().value("long", 0xffffffffffffL)
.done();
assertEquals(0xffffffffffffL, o.getNumber("long").longValue());
}
/**
* Tests a bug where longs were silently truncated to floats.
*/
@Test
void longBuilder() {
JsonObject o = JsonObject.builder().value("long", 0xffffffffffffL)
.done();
assertEquals(0xffffffffffffL, o.getNumber("long").longValue());
}
/**
* Test around the edges of the integral types.
*/
@Test
public void testAroundEdges() throws JsonParserException {
JsonArray array = JsonArray.from(Integer.MAX_VALUE,
((long) Integer.MAX_VALUE) + 1, Integer.MIN_VALUE,
((long) Integer.MIN_VALUE) - 1, Long.MAX_VALUE, BigInteger
.valueOf(Long.MAX_VALUE).add(BigInteger.ONE),
Long.MIN_VALUE,
BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE));
String json = JsonWriter.string().array(array).done();
assertEquals(
"[2147483647,2147483648,-2147483648,-2147483649,9223372036854775807,"
+ "9223372036854775808,-9223372036854775808,-9223372036854775809]",
json);
JsonArray array2 = JsonParser.array().from(json);
String json2 = JsonWriter.string().array(array2).done();
assertEquals(json, json2);
}
/**
* Test around the edges of the integral types.
*/
@Test
void aroundEdges() throws JsonParserException {
JsonArray array = JsonArray.from(Integer.MAX_VALUE,
((long) Integer.MAX_VALUE) + 1, Integer.MIN_VALUE,
((long) Integer.MIN_VALUE) - 1, Long.MAX_VALUE, BigInteger
.valueOf(Long.MAX_VALUE).add(BigInteger.ONE),
Long.MIN_VALUE,
BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE));
String json = JsonWriter.string().array(array).done();
assertEquals(
"[2147483647,2147483648,-2147483648,-2147483649,9223372036854775807,"
+ "9223372036854775808,-9223372036854775808,-9223372036854775809]",
json);
JsonArray array2 = JsonParser.array().from(json);
String json2 = JsonWriter.string().array(array2).done();
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

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

View File

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

File diff suppressed because it is too large Load Diff