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:
54
.github/workflows/codeql-analysis.yml
vendored
54
.github/workflows/codeql-analysis.yml
vendored
@@ -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
31
.github/workflows/maven.yml
vendored
Normal 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
|
@@ -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).
|
||||
|
@@ -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
103
pom.xml
@@ -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>
|
||||
|
69
src/main/java/com/grack/nanojson/CharBufferPool.java
Normal file
69
src/main/java/com/grack/nanojson/CharBufferPool.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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();
|
||||
|
13
src/main/java/com/grack/nanojson/JsonConvertible.java
Normal file
13
src/main/java/com/grack/nanojson/JsonConvertible.java
Normal 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();
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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");
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
126
src/main/java/com/grack/nanojson/LazyString.java
Normal file
126
src/main/java/com/grack/nanojson/LazyString.java
Normal 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);
|
||||
}
|
||||
}
|
29
src/test/java/com/grack/nanojson/JsonBuilderTest.java
Normal file
29
src/test/java/com/grack/nanojson/JsonBuilderTest.java
Normal 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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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
Reference in New Issue
Block a user