JsonDecoder.java
package zserio.runtime.json;
import java.math.BigInteger;
/**
* JSON value decoder.
*/
class JsonDecoder
{
/**
* Decodes the JSON value from the string.
*
* @param content String which contains encoded JSON value.
* @param pos Position from zero in content where the endoded JSON value begins.
*
* @return Decoder result object.
*/
public static Result decodeValue(String content, int pos)
{
if (pos >= content.length())
return Result.failure();
final char firstChar = content.charAt(pos);
switch (firstChar)
{
case 'n':
return decodeLiteral(content, pos, "null", null);
case 't':
return decodeLiteral(content, pos, "true", true);
case 'f':
return decodeLiteral(content, pos, "false", false);
case 'N':
return decodeLiteral(content, pos, "NaN", Double.NaN);
case 'I':
return decodeLiteral(content, pos, "Infinity", Double.POSITIVE_INFINITY);
case '"':
return decodeString(content, pos);
case '-':
if (pos + 1 >= content.length())
return Result.failure(1);
final char secondChar = content.charAt(pos + 1);
if (secondChar == 'I')
return decodeLiteral(content, pos, "-Infinity", Double.NEGATIVE_INFINITY);
return decodeNumber(content, pos);
default:
return decodeNumber(content, pos);
}
}
/**
* Decoder result value.
*/
static class Result
{
/**
* Gets the decoder result.
*
* @return true in case of success, otherwise false.
*/
public boolean success()
{
return success;
}
/**
* Gets the decoded JSON value.
*
* @return Decoded JSON value or null in case of failure.
*/
public Object getValue()
{
return value;
}
/**
* Gets the number of read characters from the string which contains encoded JSON value.
*
* In case of failure, it returns the number of processed (read) characters.
*
* @return Number of read characters.
*/
public int getNumReadChars()
{
return numReadChars;
}
public static Result failure()
{
return new Result(false, null, 0);
}
public static Result failure(int numReadChars)
{
return new Result(false, null, numReadChars);
}
public static Result success(Object value, int numReadChars)
{
return new Result(true, value, numReadChars);
}
private Result(boolean success, Object value, int numReadChars)
{
this.success = success;
this.value = value;
this.numReadChars = numReadChars;
}
private final boolean success;
private final Object value;
private final int numReadChars;
}
private static Result decodeLiteral(String content, int pos, String text, Object decodedValue)
{
final int textLength = text.length();
if (pos + textLength > content.length())
return Result.failure(content.length() - pos);
final String subContent = content.substring(pos, pos + textLength);
if (subContent.equals(text))
return Result.success(decodedValue, textLength);
return Result.failure(textLength);
}
private static Result decodeString(String content, int pos)
{
final StringBuilder decodedString = new StringBuilder();
int endOfStringPos = pos + 1; // we know that at the beginning is '"'
while (true)
{
if (endOfStringPos >= content.length())
return Result.failure(endOfStringPos - pos);
final char nextChar = content.charAt(endOfStringPos);
endOfStringPos++;
if (nextChar == '\\')
{
if (endOfStringPos >= content.length())
return Result.failure(endOfStringPos - pos);
final char nextNextChar = content.charAt(endOfStringPos);
endOfStringPos++;
switch (nextNextChar)
{
case '\\':
case '"':
decodedString.append(nextNextChar);
break;
case 'b':
decodedString.append('\b');
break;
case 'f':
decodedString.append('\f');
break;
case 'n':
decodedString.append('\n');
break;
case 'r':
decodedString.append('\r');
break;
case 't':
decodedString.append('\t');
break;
case 'u': // unicode escape
final int unicodeEscapeLen = 4;
endOfStringPos += unicodeEscapeLen;
if (endOfStringPos >= content.length())
return Result.failure(content.length() - pos);
final String subContent =
content.substring(endOfStringPos - unicodeEscapeLen, endOfStringPos);
final String decodedUnicode = decodeUnicodeEscape(subContent);
if (decodedUnicode != null)
decodedString.append(decodedUnicode);
else
return Result.failure(endOfStringPos - pos);
break;
default:
// unknown escape character, not decoded...
return Result.failure(endOfStringPos - pos);
}
}
else if (nextChar == '"')
{
break;
}
else
{
decodedString.append(nextChar);
}
}
return Result.success(decodedString.toString(), endOfStringPos - pos);
}
private static String decodeUnicodeEscape(String content)
{
try
{
return new String(Character.toChars(Integer.parseInt(content, 16)));
}
catch (NumberFormatException excpt)
{
return null;
}
}
private static Result decodeNumber(String content, int pos)
{
final ExtractNumberResult result = extractNumber(content, pos);
final String numberContent = result.getNumberContent();
final int numberLength = numberContent.length();
if (numberLength == 0)
return Result.failure(1);
try
{
if (result.isDouble())
{
final Double doubleNumber = Double.parseDouble(numberContent);
return Result.success(doubleNumber, numberLength);
}
else
{
final BigInteger integerNumber = new BigInteger(numberContent);
return Result.success(integerNumber, numberLength);
}
}
catch (NumberFormatException excpt)
{
return Result.failure(numberLength);
}
}
private static class ExtractNumberResult
{
public ExtractNumberResult(String numberContent, boolean isDouble)
{
this.numberContent = numberContent;
this.isDouble = isDouble;
}
public String getNumberContent()
{
return numberContent;
}
public boolean isDouble()
{
return isDouble;
}
private String numberContent;
private boolean isDouble;
}
private static ExtractNumberResult extractNumber(String content, int pos)
{
int endOfNumberPos = pos;
if (content.charAt(endOfNumberPos) == '-') // we already know that there is something after '-'
endOfNumberPos++;
boolean acceptExpSign = false;
boolean isScientificDouble = false;
boolean isDouble = false;
while (endOfNumberPos < content.length())
{
final char nextChar = content.charAt(endOfNumberPos);
if (acceptExpSign)
{
acceptExpSign = false;
if (nextChar == '+' || nextChar == '-')
{
endOfNumberPos++;
continue;
}
}
if (Character.isDigit(nextChar))
{
endOfNumberPos++;
continue;
}
if ((nextChar == 'e' || nextChar == 'E') && !isScientificDouble)
{
endOfNumberPos++;
isDouble = true;
isScientificDouble = true;
acceptExpSign = true;
continue;
}
if (nextChar == '.' && !isDouble)
{
endOfNumberPos++;
isDouble = true;
continue;
}
break;
}
return new ExtractNumberResult(content.substring(pos, endOfNumberPos), isDouble);
}
}