diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 72125ecd3..4e2485c1b 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -717,7 +717,10 @@ public class Utilities { return result; } - + public static boolean isTokenChar(char ch) { + return isAlphabetic(ch) || (ch == '_'); + } + public static boolean isDigit(char c) { return (c >= '0') && (c <= '9'); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonException.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonException.java new file mode 100644 index 000000000..beaba2163 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonException.java @@ -0,0 +1,9 @@ +package org.hl7.fhir.utilities.json; + +public class JsonException extends Exception { + + public JsonException(String msg) { + // TODO Auto-generated constructor stub + } + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonArray.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonArray.java new file mode 100644 index 000000000..7a46c1acc --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonArray.java @@ -0,0 +1,89 @@ +package org.hl7.fhir.utilities.json.model; + +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.utilities.json.JsonException; + + +public class JsonArray extends JsonElement { + private List items = new ArrayList<>(); + private List noCommas; // validator use + private List unQuoted; // validator use + + public List asStrings() { + List list = new ArrayList<>(); + for (JsonElement n : items) { + if (n instanceof JsonPrimitive) { + list.add(n.toString()); + } + } + return list; + } + + public List getItems() { + return items; + } + + public List asObjects() { + List list = new ArrayList<>(); + for (JsonElement n : items) { + if (n instanceof JsonObject) { + list.add((JsonObject) n); + } + } + return list; + } + + public JsonElementType elementType() { + return JsonElementType.ARRAY; + } + + public JsonArray add(JsonElement node) throws JsonException { + check(node != null, "null object in JsonArray.add()"); + items.add(node); + return this; + } + + public JsonArray add(String value) throws JsonException { + check(value != null, "null value in JsonArray.add()"); + items.add(new JsonString(value)); + return this; + } + + public Integer size() { + return items.size(); + } + + public boolean isNoComma(int i) { + return noCommas == null ? false : noCommas.get(i); + } + + public boolean isUnquoted(int i) { + return unQuoted == null ? false : unQuoted.get(i); + } + + // for the parser only + public void addForParser(JsonElement e, boolean itemNoComma, boolean unquoted) throws JsonException { + check(e != null, "null object in JsonArray.add()"); + items.add(e); + if (noCommas == null) { + noCommas = new ArrayList<>(); + } + noCommas.add(itemNoComma); + if (unQuoted == null) { + unQuoted = new ArrayList<>(); + } + unQuoted.add(unquoted); + } + + + public JsonObject findByStringProp(JsonArray arr, String prop, String value) { + for (JsonObject obj : asObjects()) { + if (obj.has(prop) && value.equals(obj.getString(prop))) + return obj; + } + return null; + } + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonBoolean.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonBoolean.java new file mode 100644 index 000000000..9d63174f2 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonBoolean.java @@ -0,0 +1,31 @@ +package org.hl7.fhir.utilities.json.model; + +public class JsonBoolean extends JsonPrimitive { + private boolean value; + + public JsonBoolean(boolean value) { + super(); + this.value = value; + } + + public JsonElementType elementType() { + return JsonElementType.BOOLEAN; + } + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getValue() { + return value ? "true" : "false"; + } + + @Override + public String toString() { + return getValue(); + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonComment.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonComment.java new file mode 100644 index 000000000..c5fb78ba0 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonComment.java @@ -0,0 +1,5 @@ +package org.hl7.fhir.utilities.json.model; + +public class JsonComment { + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonElement.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonElement.java new file mode 100644 index 000000000..456792aae --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonElement.java @@ -0,0 +1,48 @@ +package org.hl7.fhir.utilities.json.model; + +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.utilities.json.JsonException; + +public abstract class JsonElement { + + private List comments; + private JsonLocationData start; + private JsonLocationData end; + + public abstract JsonElementType elementType(); + + public List getComments() { + if (comments == null ) { + comments = new ArrayList<>(); + } + return comments; + } + + public JsonLocationData getStart() { + return start; + } + + public void setStart(JsonLocationData start) { + this.start = start; + } + + public JsonLocationData getEnd() { + return end; + } + + public void setEnd(JsonLocationData end) { + this.end = end; + } + + protected void check(boolean test, String msg) throws JsonException { + if (!test) { + throw new JsonException(msg); + } + } + + public boolean hasComments() { + return comments != null && !comments.isEmpty(); + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonElementType.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonElementType.java new file mode 100644 index 000000000..5aa69ba10 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonElementType.java @@ -0,0 +1,5 @@ +package org.hl7.fhir.utilities.json.model; + +public enum JsonElementType { + OBJECT, ARRAY, STRING, NUMBER, BOOLEAN, NULL; +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonLocationData.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonLocationData.java new file mode 100644 index 000000000..b0b7c8de5 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonLocationData.java @@ -0,0 +1,59 @@ +package org.hl7.fhir.utilities.json.model; + +public class JsonLocationData { + private int line; + private int col; + private int lastCol; + + public JsonLocationData(int line, int col) { + super(); + this.line = line; + this.col = col; + this.lastCol = col; + } + + public int getLine() { + return line; + } + + public int getCol() { + return col; + } + + public void newLine() { + line++; + lastCol = col; + col = 1; + } + + public JsonLocationData copy() { + return new JsonLocationData(line, col); + } + + public void incCol() { + col++; + } + + @Override + public String toString() { + return "(" + line + ", " + col + ")"; + } + + public JsonLocationData prev() { + if (col == 1) { + return new JsonLocationData(line-1, lastCol); + } else { + return new JsonLocationData(line, col-1); + } + } + + public void back() { + if (col == 1) { + line--; + line = lastCol; + } else { + col--; + } + } + +} \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNull.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNull.java new file mode 100644 index 000000000..2b5f15c65 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNull.java @@ -0,0 +1,18 @@ +package org.hl7.fhir.utilities.json.model; + +public class JsonNull extends JsonPrimitive { + + public JsonElementType elementType() { + return JsonElementType.NULL; + } + + + public String getValue() { + return "null"; + } + + @Override + public String toString() { + return getValue(); + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNumber.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNumber.java new file mode 100644 index 000000000..58cc923cc --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonNumber.java @@ -0,0 +1,33 @@ +package org.hl7.fhir.utilities.json.model; + +import org.hl7.fhir.utilities.Utilities; + +public class JsonNumber extends JsonPrimitive { + + private String value; + + public JsonNumber(String value) { + this.value = value; + } + + public JsonElementType elementType() { + return JsonElementType.NUMBER; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return value; + } + + public Integer getInteger() { + if (Utilities.isInteger(value)) { + return Integer.parseInt(value); + } else { + return null; + } + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java new file mode 100644 index 000000000..1acd3228a --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java @@ -0,0 +1,127 @@ +package org.hl7.fhir.utilities.json.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hl7.fhir.utilities.json.JsonException; + + +public class JsonObject extends JsonElement { + + private List properties = new ArrayList<>(); + private Map propMap = new HashMap<>(); + + public JsonElementType elementType() { + return JsonElementType.OBJECT; + } + + public JsonObject add(String name, JsonElement value) throws JsonException { + check(name != null, "Numm is null"); + check(value != null, "Numm is null"); + check(get(name) == null, "Name '"+name+"' already exists"); + JsonProperty p = new JsonProperty(name, value); + properties.add(p); + propMap.put(name, p); + return this; + } + + // this is used by the parser which can allow duplicates = true (for the validator). You should not otherwise use it + public JsonObject addForParser(String name, JsonElement value, boolean noComma, boolean nameUnquoted, boolean valueUnquoted) throws JsonException { + check(name != null, "Numm is null"); + check(value != null, "Numm is null"); + JsonProperty p = new JsonProperty(name, value); + p.setNoComma(noComma); + p.setUnquotedName(nameUnquoted); + p.setUnquotedValue(valueUnquoted); + properties.add(p); + propMap.put(name, p); // last duplicate wins + return this; + } + + public JsonObject add(String name, String value) throws JsonException { + return add(name, new JsonString(value)); + } + + public JsonObject add(String name, boolean value) throws JsonException { + return add(name, new JsonBoolean(value)); + } + + + public JsonElement get(String name) { + if (propMap.containsKey(name)) { + return propMap.get(name).getValue(); + } else { + return null; + } + } + + public boolean has(String name) { + return propMap.containsKey(name); + } + + public List getProperties() { + return properties; + } + + public String str(String name) { + if (has(name)) { + return get(name).toString(); + } else { + return null; + } + } + + public JsonObject getObj(String name) { + return (JsonObject) get(name); + } + + public JsonString getStr(String name) { + return (JsonString) get(name); + } + + public JsonBoolean getBool(String name) { + return (JsonBoolean) get(name); + } + + public JsonNumber getNum(String name) { + return (JsonNumber) get(name); + } + + public JsonNull getNull(String name) { + return (JsonNull) get(name); + } + + public JsonArray getArr(String name) { + return (JsonArray) get(name); + } + + public Integer getInteger(String name) { + return ((JsonNumber) get(name)).getInteger(); + } + + public String getString(String name) { + return ((JsonPrimitive) get(name)).toString(); + } + + public Boolean getBoolean(String name) { + return ((JsonBoolean) get(name)).isValue(); + } + + public JsonObject forceObj(String name) throws JsonException { + if (!has(name)) { + add(name, new JsonObject()); + } + return getObj(name); + } + + public JsonArray forceArr(String name) throws JsonException { + if (!has(name)) { + add(name, new JsonArray()); + } + return getArr(name); + } + + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonPrimitive.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonPrimitive.java new file mode 100644 index 000000000..b2b280469 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonPrimitive.java @@ -0,0 +1,5 @@ +package org.hl7.fhir.utilities.json.model; + +public abstract class JsonPrimitive extends JsonElement { + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonProperty.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonProperty.java new file mode 100644 index 000000000..7999d279b --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonProperty.java @@ -0,0 +1,53 @@ +package org.hl7.fhir.utilities.json.model; + +public class JsonProperty { + private String name; + private JsonElement value; + + boolean noComma; // parse in Json5 mode, but records this so the validator can complain + boolean unquotedName; + boolean unquotedValue; + + public JsonProperty(String name, JsonElement value) { + super(); + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public JsonElement getValue() { + return value; + } + + public void setValue(JsonElement value) { + this.value = value; + } + + public boolean isNoComma() { + return noComma; + } + + public void setNoComma(boolean noComma) { + this.noComma = noComma; + } + + public boolean isUnquotedName() { + return unquotedName; + } + + public void setUnquotedName(boolean unquotedName) { + this.unquotedName = unquotedName; + } + + public boolean isUnquotedValue() { + return unquotedValue; + } + + public void setUnquotedValue(boolean unquotedValue) { + this.unquotedValue = unquotedValue; + } + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonString.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonString.java new file mode 100644 index 000000000..978650c49 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonString.java @@ -0,0 +1,28 @@ +package org.hl7.fhir.utilities.json.model; + +public class JsonString extends JsonPrimitive { + private String value; + + public JsonString(String value) { + super(); + this.value = value; + } + + public JsonElementType elementType() { + return JsonElementType.STRING; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonLexer.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonLexer.java new file mode 100644 index 000000000..0ab21bbe7 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonLexer.java @@ -0,0 +1,297 @@ +package org.hl7.fhir.utilities.json.parser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.model.JsonElement; +import org.hl7.fhir.utilities.json.model.JsonLocationData; + +public class JsonLexer { + public static class State { + private String name; + private boolean isProp; + public State(String name, boolean isProp) { + super(); + this.name = name; + this.isProp = isProp; + } + public String getName() { + return name; + } + public boolean isProp() { + return isProp; + } + } + + public enum TokenType { + Open, Close, String, Number, Colon, Comma, OpenArray, CloseArray, Eof, Null, Boolean; + + boolean isValueType() { + return this == Open || this == String || this == Number || this == OpenArray || this == Boolean || this == Null; + } + } + + private String source; + private int cursor; + private String peek; + private String value; + private TokenType type; + private Stack states = new Stack(); + private JsonLocationData lastLocationBWS; + private JsonLocationData lastLocationAWS; + private JsonLocationData location; + private StringBuilder b = new StringBuilder(); + private boolean allowComments; + private boolean allowUnquotedStrings; + private List comments = new ArrayList<>(); + private boolean isUnquoted; + + public JsonLexer(String source, boolean allowComments, boolean allowUnquotedStrings) throws IOException { + this.source = source; + this.allowComments = allowComments; + this.allowUnquotedStrings = allowUnquotedStrings; + cursor = -1; + location = new JsonLocationData(1, 1); + start(); + } + + private boolean more() { + return peek != null || cursor < source.length(); + } + + private String getNext(int length) throws IOException { + String result = ""; + if (peek != null) { + if (peek.length() > length) { + result = peek.substring(0, length); + peek = peek.substring(length); + } else { + result = peek; + peek = null; + } + } + if (result.length() < length) { + int len = length - result.length(); + if (cursor > source.length() - len) + throw error("Attempt to read past end of source"); + result = result + source.substring(cursor+1, cursor+len+1); + cursor = cursor + len; + } + for (char ch : result.toCharArray()) + if (ch == '\n') + location.newLine(); + else + location.incCol(); + return result; + } + + private char getNextChar() throws IOException { + char ch; + if (peek != null) { + ch = peek.charAt(0); + peek = peek.length() == 1 ? null : peek.substring(1); + } else { + cursor++; + if (cursor >= source.length()) { + ch = 0; + } else { + ch = source.charAt(cursor); + } + } + if (ch == '\n') { + location.newLine(); + } else { + location.incCol(); + } + return ch; + } + + private void push(char ch){ + peek = peek == null ? String.valueOf(ch) : String.valueOf(ch)+peek; + location.back(); + } + + public IOException error(String msg) { + return new IOException("Error parsing JSON source: "+msg+" at Line "+Integer.toString(location.getLine())+" (path=["+path()+"])"); + } + + private String path() { + if (states.empty()) + return value; + else { + String result = ""; + for (State s : states) + result = result + '/'+ s.getName(); + result = result + value; + return result; + } + } + + public void start() throws IOException { + // char ch = getNextChar(); + // if (ch = '\.uEF') + // begin + // // skip BOM + // getNextChar(); + // getNextChar(); + // end + // else + // push(ch); + next(); + } + + public TokenType getType() { + return type; + } + + public String getValue() { + return value; + } + + + public JsonLocationData getLastLocationBWS() { + return lastLocationBWS; + } + + public JsonLocationData getLastLocationAWS() { + return lastLocationAWS; + } + + public void next() throws IOException { + lastLocationBWS = location.copy(); + char ch; + do { + ch = getNextChar(); + if (allowComments && ch == '/') { + char ch1 = getNextChar(); + if (ch1 == '/') { + StringBuilder b = new StringBuilder(); + boolean first = true; + while (more() && !Utilities.charInSet(ch, '\r', '\n')) { + if (first) first = false; else b.append(ch); + ch = getNextChar(); + } + comments.add(b.toString().trim()); + } else { + push(ch1); + } + } + } while (more() && Utilities.charInSet(ch, ' ', '\r', '\n', '\t')); + lastLocationAWS = location.copy().prev(); + isUnquoted = false; + + if (!more()) { + type = TokenType.Eof; + } else { + switch (ch) { + case '{' : + type = TokenType.Open; + break; + case '}' : + type = TokenType.Close; + break; + case '"' : + type = TokenType.String; + b.setLength(0); + do { + ch = getNextChar(); + if (ch == '\\') { + ch = getNextChar(); + switch (ch) { + case '"': b.append('"'); break; + case '\'': b.append('\''); break; + case '\\': b.append('\\'); break; + case '/': b.append('/'); break; + case 'n': b.append('\n'); break; + case 'r': b.append('\r'); break; + case 't': b.append('\t'); break; + case 'u': b.append((char) Integer.parseInt(getNext(4), 16)); break; + default : + throw error("unknown escape sequence: \\"+ch); + } + ch = ' '; + } else if (ch != '"') + b.append(ch); + } while (more() && (ch != '"')); + if (!more()) + throw error("premature termination of json stream during a string"); + value = b.toString(); + break; + case ':' : + type = TokenType.Colon; + break; + case ',' : + type = TokenType.Comma; + break; + case '[' : + type = TokenType.OpenArray; + break; + case ']' : + type = TokenType.CloseArray; + break; + default: + if ((ch >= '0' && ch <= '9') || ch == '-') { + type = TokenType.Number; + b.setLength(0); + while (more() && ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.') || ch == '+' || ch == 'e' || ch == 'E') { + b.append(ch); + ch = getNextChar(); + } + value = b.toString(); + push(ch); + } else if (Utilities.isAlphabetic(ch) || (ch == '_')) { + type = TokenType.String; + isUnquoted = true; + b.setLength(0); + while (more() && (Utilities.isAlphabetic(ch) || Utilities.isDigit(ch) || Utilities.existsInList(ch, '_', '.', '-'))) { + b.append(ch); + ch = getNextChar(); + } + value = b.toString(); + push(ch); + if ("true".equals(value) || "false".equals(value)) { + this.type = TokenType.Boolean; + isUnquoted = false; + } else if ("null".equals(value)) { + this.type = TokenType.Null; + isUnquoted = false; + } else if (!allowUnquotedStrings) { + throw error("Unexpected token '"+value+"' in json stream"); + } + } + } + } + } + + public String consume(TokenType type) throws IOException { + if (this.type != type) + throw error("JSON syntax error - found "+this.type.toString()+" expecting "+type.toString()); + String result = value; + next(); + return result; + } + + public JsonLocationData getLocation() { + return location; + } + + public Stack getStates() { + return states; + } + + public void takeComments(JsonElement child) { + if (!comments.isEmpty()) { + child.getComments().addAll(comments); + comments.clear(); + } + } + + public boolean isUnquoted() { + return isUnquoted; + } + + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonParser.java new file mode 100644 index 000000000..5d9a65455 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/parser/JsonParser.java @@ -0,0 +1,645 @@ +package org.hl7.fhir.utilities.json.parser; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.JsonException; +import org.hl7.fhir.utilities.json.model.JsonArray; +import org.hl7.fhir.utilities.json.model.JsonBoolean; +import org.hl7.fhir.utilities.json.model.JsonElement; +import org.hl7.fhir.utilities.json.model.JsonElementType; +import org.hl7.fhir.utilities.json.model.JsonLocationData; +import org.hl7.fhir.utilities.json.model.JsonNull; +import org.hl7.fhir.utilities.json.model.JsonNumber; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.json.model.JsonPrimitive; +import org.hl7.fhir.utilities.json.model.JsonProperty; +import org.hl7.fhir.utilities.json.model.JsonString; +import org.hl7.fhir.utilities.json.parser.JsonLexer.State; +import org.hl7.fhir.utilities.json.parser.JsonLexer.TokenType; + +/** + * Simple parser for JSON. This parser is not particularly quick (though it's not slow) + * The focus for this parser is to faithfully record the line/col number of json elements + * so that the FHIR validator can report issues by line number + * + * Also, for the validator, the parser will accept duplicate property names + * + * JSON5: When running in Json5 mode, the parser accepts + * * unquoted strings for both fields and values + * * missing commas in objects and arrays + * * comments - anything starting // will be processed as a comma to the end of the line + * + * The FHIR Validator uses this parser in Json5 mode, and the object model is marked up + * with deviations from base JSON spec so that the validator can record them as errors + * (this is better than blowing up parsing the JSON) + * + * @author grahamegrieve + * + */ +public class JsonParser { + + public static JsonObject parseObject(InputStream stream) throws IOException, JsonException { + return parseObject(TextFile.streamToString(stream)); + } + + public static JsonObject parseObject(byte[] stream) throws IOException, JsonException { + return parseObject(TextFile.bytesToString(stream)); + } + + public static JsonObject parseObject(String source) throws IOException, JsonException { + return parseObject(source, false); + } + + public static JsonObject parseObject(File source) throws IOException, JsonException { + return parseObject(TextFile.fileToString(source)); + } + + public static JsonObject parseObjectFromFile(String source) throws IOException, JsonException { + return parseObject(TextFile.fileToString(source)); + } + + public static JsonObject parseObjectFromUrl(String source) throws IOException, JsonException { + return parseObject(fetch(source)); + } + + public static JsonObject parseObject(InputStream stream, boolean isJson5) throws IOException, JsonException { + return parseObject(TextFile.streamToString(stream), isJson5); + } + + public static JsonObject parseObject(byte[] stream, boolean isJson5) throws IOException, JsonException { + return parseObject(TextFile.bytesToString(stream), isJson5); + } + + public static JsonObject parseObject(String source, boolean isJson5) throws IOException, JsonException { + return parseObject(source, isJson5, false); + } + + public static JsonObject parseObjectFromUrl(String source, boolean isJson5) throws IOException, JsonException { + return parseObject(fetch(source), isJson5); + } + + public static JsonObject parseObject(InputStream stream, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + return parseObject(TextFile.streamToString(stream), isJson5, allowDuplicates); + } + + public static JsonObject parseObject(byte[] stream, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + return parseObject(TextFile.bytesToString(stream), isJson5, allowDuplicates); + } + + public static JsonObject parseObject(String source, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + return new JsonParser().parseJsonObject(source, isJson5, allowDuplicates); + } + + // ================================================================ + + public static JsonElement parse(InputStream stream) throws IOException, JsonException { + return parse(TextFile.streamToString(stream)); + } + + public static JsonElement parse(byte[] stream) throws IOException, JsonException { + return parse(TextFile.bytesToString(stream)); + } + + public static JsonElement parse(String source) throws IOException, JsonException { + return parse(source, false); + } + + public static JsonElement parse(File source) throws IOException, JsonException { + return parse(TextFile.fileToString(source)); + } + + public static JsonElement parseFromFile(String source) throws IOException, JsonException { + return parse(TextFile.fileToString(source)); + } + + public static JsonElement parseFromUrl(String source) throws IOException, JsonException { + return parse(fetch(source)); + } + + public static JsonElement parse(InputStream stream, boolean isJson5) throws IOException, JsonException { + return parse(TextFile.streamToString(stream), isJson5); + } + + public static JsonElement parse(byte[] stream, boolean isJson5) throws IOException, JsonException { + return parse(TextFile.bytesToString(stream), isJson5); + } + + public static JsonElement parse(String source, boolean isJson5) throws IOException, JsonException { + return parse(source, isJson5, false); + } + + public static JsonElement parseFromUrl(String source, boolean isJson5) throws IOException, JsonException { + return parse(fetch(source), isJson5); + } + + public static JsonElement parse(InputStream stream, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + return parse(TextFile.streamToString(stream), isJson5, allowDuplicates); + } + + public static JsonElement parse(byte[] stream, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + return parse(TextFile.bytesToString(stream), isJson5, allowDuplicates); + } + + public static JsonElement parse(String source, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + return new JsonParser().parseJsonElement(source, isJson5, allowDuplicates); + } + + + // ================================================================ + + public static String compose(JsonElement element) { + return compose(element, false); + } + + public static void compose(JsonElement element, OutputStream stream) throws IOException { + compose(element, stream, false); + } + + public static byte[] composeBytes(JsonElement element) { + return composeBytes(element, false); + } + + public static String compose(JsonElement element, boolean pretty) { + return new JsonParser().write(element, pretty); + } + + public static void compose(JsonElement element, OutputStream stream, boolean pretty) throws IOException { + byte[] cnt = composeBytes(element, pretty); + stream.write(cnt); + } + + public static byte[] composeBytes(JsonElement element, boolean pretty) { + String s = compose(element); + return s.getBytes(StandardCharsets.UTF_8); + } + + // ================================================================ + + enum ItemType { + Object, String, Number, Boolean, Array, End, Eof, Null; + } + private JsonLexer lexer; + private ItemType itemType = ItemType.Object; + private String itemName; + private String itemValue; + private boolean allowDuplicates = true; + private boolean allowComments = false; + private boolean allowNoComma = false; + private JsonLocationData startProperty; + private JsonLocationData endProperty; + private boolean itemNoComma; + private boolean allowUnquotedStrings; + private boolean itemUnquoted; + private boolean valueUnquoted; + + private JsonObject parseJsonObject(String source, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + this.allowDuplicates = allowDuplicates; + this.allowComments = isJson5; + this.allowNoComma = isJson5; + this.allowUnquotedStrings = isJson5; + return parseSource(Utilities.stripBOM(source)); + } + + private JsonObject parseSource(String source) throws IOException, JsonException { + lexer = new JsonLexer(source, allowComments, allowUnquotedStrings); + JsonObject result = new JsonObject(); + lexer.takeComments(result); + result.setStart(lexer.getLastLocationAWS().copy()); + if (lexer.getType() == TokenType.Open) { + lexer.next(); + lexer.getStates().push(new State("", true)); + } + else + throw lexer.error("Unexpected content at start of JSON: "+lexer.getType().toString()); + + if (lexer.getType() != TokenType.Close) { + parseProperty(); + readObject("$", result, true); + } + result.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy()); + return result; + } + + private JsonElement parseJsonElement(String source, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException { + this.allowDuplicates = allowDuplicates; + this.allowComments = isJson5; + this.allowNoComma = isJson5; + this.allowUnquotedStrings = isJson5; + return parseSourceElement(Utilities.stripBOM(source)); + } + + private JsonElement parseSourceElement(String source) throws IOException, JsonException { + lexer = new JsonLexer(source, allowComments, allowUnquotedStrings); + switch (lexer.getType()) { + case Boolean: + JsonBoolean bool = new JsonBoolean(lexer.getValue().equals("true")); + lexer.takeComments(bool); + bool.setStart(lexer.getLastLocationAWS().copy()); + bool.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy()); + return bool; + case Null: + JsonNull nll = new JsonNull(); + lexer.takeComments(nll); + nll.setStart(lexer.getLastLocationAWS().copy()); + nll.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy()); + return nll; + case Number: + JsonNumber num = new JsonNumber(lexer.getValue()); + lexer.takeComments(num); + num.setStart(lexer.getLastLocationAWS().copy()); + num.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy()); + return num; + case Open: + JsonObject obj = new JsonObject(); + lexer.takeComments(obj); + obj.setStart(lexer.getLastLocationAWS().copy()); + if (lexer.getType() == TokenType.Open) { + lexer.next(); + lexer.getStates().push(new State("", true)); + } + else + throw lexer.error("Unexpected content at start of JSON: "+lexer.getType().toString()); + + if (lexer.getType() != TokenType.Close) { + parseProperty(); + readObject("$", obj, true); + } + obj.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy()); + return obj; + case OpenArray: + JsonArray arr = new JsonArray(); + lexer.takeComments(arr); + arr.setStart(lexer.getLastLocationAWS().copy()); + lexer.next(); + lexer.getStates().push(new State("", false)); + if (lexer.getType() != TokenType.CloseArray) { + parseProperty(); + readArray("$", arr, true); + } + arr.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy()); + return arr; + case String: + JsonString str = new JsonString(lexer.getValue()); + lexer.takeComments(str); + str.setStart(lexer.getLastLocationAWS().copy()); + str.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy()); + return str; + default: + } + throw lexer.error("Unexpected content at start of JSON: "+lexer.getType().toString()); + } + + private void readObject(String path, JsonObject obj, boolean root) throws IOException, JsonException { + while (!(itemType == ItemType.End) || (root && (itemType == ItemType.Eof))) { + switch (itemType) { + case Object: + JsonObject child = new JsonObject(); //(obj.path+'.'+ItemName); + child.setStart(startProperty.copy()); + lexer.takeComments(child); + if (obj.has(itemName) && !allowDuplicates) + throw lexer.error("Duplicated property name: "+itemName+ " @ "+path); + obj.addForParser(itemName, child, itemNoComma, itemUnquoted, valueUnquoted); + next(); + readObject(path+"."+itemName, child, false); + child.setEnd(endProperty.copy()); + break; + case Boolean : + JsonBoolean childB = new JsonBoolean(Boolean.valueOf(itemValue)); + childB.setStart(startProperty.copy()); + lexer.takeComments(childB); + if (obj.has(itemName) && !allowDuplicates) + throw lexer.error("Duplicated property name: "+itemName+ " @ "+path); + obj.addForParser(itemName, childB, itemNoComma, itemUnquoted, valueUnquoted); + childB.setEnd(endProperty.copy()); + break; + case String: + JsonString childS = new JsonString(itemValue); + childS.setStart(startProperty.copy()); + lexer.takeComments(childS); + if (obj.has(itemName) && !allowDuplicates) + throw lexer.error("Duplicated property name: "+itemName+ " @ "+path); + obj.addForParser(itemName, childS, itemNoComma, itemUnquoted, valueUnquoted); + childS.setEnd(endProperty.copy()); + break; + case Number: + JsonNumber childN = new JsonNumber(itemValue); + childN.setStart(startProperty.copy()); + lexer.takeComments(childN); + if (obj.has(itemName) && !allowDuplicates) + throw lexer.error("Duplicated property name: "+itemName+ " @ "+path); + obj.addForParser(itemName, childN, itemNoComma, itemUnquoted, valueUnquoted); + childN.setEnd(endProperty.copy()); + break; + case Null: + JsonNull childn = new JsonNull(); + childn.setStart(startProperty.copy()); + lexer.takeComments(childn); + if (obj.has(itemName) && !allowDuplicates) + throw lexer.error("Duplicated property name: "+itemName+ " @ "+path); + obj.addForParser(itemName, childn, itemNoComma, itemUnquoted, valueUnquoted); + childn.setEnd(endProperty.copy()); + break; + case Array: + JsonArray childA = new JsonArray(); // (obj.path+'.'+ItemName); + childA.setStart(startProperty.copy()); + lexer.takeComments(childA); + if (obj.has(itemName) && !allowDuplicates) + throw lexer.error("Duplicated property name: "+itemName+ " @ "+path); + obj.addForParser(itemName, childA, itemNoComma, itemUnquoted, valueUnquoted); + next(); + if (!readArray(path+"."+itemName, childA, false)) + next(true); + if (childA.getEnd() == null) { + childA.setEnd(endProperty.copy()); + } + break; + case Eof : + throw lexer.error("Unexpected End of File"); + case End: + throw lexer.error("Unexpected End"); // Don't think we can get here + } + itemNoComma = false; + endProperty = lexer.getLocation().copy(); + next(); + } + } + + private boolean readArray(String path, JsonArray arr, boolean root) throws IOException, JsonException { + boolean res = false; + while (!((itemType == ItemType.End) || (root && (itemType == ItemType.Eof)))) { + res = true; + switch (itemType) { + case Object: + JsonObject obj = new JsonObject(); // (arr.path+'['+inttostr(i)+']'); + obj.setStart(startProperty.copy()); + lexer.takeComments(obj); + arr.addForParser(obj, itemNoComma, valueUnquoted); + next(); + readObject(path+"["+(arr.size()-1)+"]", obj, false); + obj.setEnd(endProperty.copy()); + break; + case String: + JsonString s = new JsonString(itemValue); + s.setStart(startProperty.copy()); + lexer.takeComments(s); + arr.addForParser(s, itemNoComma, valueUnquoted); + s.setEnd(endProperty.copy()); + break; + case Number: + JsonNumber n = new JsonNumber(itemValue); + n.setStart(startProperty.copy()); + lexer.takeComments(n); + arr.addForParser(n, itemNoComma, valueUnquoted); + n.setEnd(endProperty.copy()); + break; + case Boolean: + JsonBoolean b = new JsonBoolean("true".equals(itemValue)); + b.setStart(startProperty.copy()); + lexer.takeComments(b); + arr.addForParser(b, itemNoComma, valueUnquoted); + b.setEnd(endProperty.copy()); + break; + case Null : + JsonNull nn = new JsonNull(); + nn.setStart(startProperty.copy()); + lexer.takeComments(nn); + arr.addForParser(nn, itemNoComma, valueUnquoted); + nn.setEnd(endProperty.copy()); + break; + case Array: + JsonArray child = new JsonArray(); // (arr.path+'['+inttostr(i)+']'); + child.setStart(startProperty.copy()); + lexer.takeComments(child); + arr.addForParser(child, itemNoComma, valueUnquoted); + next(); + readArray(path+"["+(arr.size()-1)+"]", child, false); + child.setEnd(endProperty.copy()); + break; + case Eof : + throw lexer.error("Unexpected End of File"); + case End: + throw lexer.error("Can't get here"); + } + itemNoComma = false; + arr.setEnd(lexer.getLocation().copy()); + next(); + } + return res; + } + + private void next() throws IOException { + next(false); + } + + private void next(boolean noPop) throws IOException { + switch (itemType) { + case Object : + lexer.consume(TokenType.Open); + lexer.getStates().push(new State(itemName, true)); + if (lexer.getType() == TokenType.Close) { + itemType = ItemType.End; + lexer.next(); + } else + parseProperty(); + break; + case Null: + case String: + case Number: + case End: + case Boolean : + if (itemType == ItemType.End && !noPop) + lexer.getStates().pop(); + if (lexer.getType() == TokenType.Comma) { + lexer.next(); + parseProperty(); + } else if (lexer.getType() == TokenType.Close) { + itemType = ItemType.End; + lexer.next(); + } else if (lexer.getType() == TokenType.CloseArray) { + itemType = ItemType.End; + lexer.next(); + } else if (lexer.getType() == TokenType.Eof) { + itemType = ItemType.Eof; + } else if (allowNoComma && (lexer.getType() == TokenType.String || (!lexer.getStates().peek().isProp()) && lexer.getType().isValueType())) { + itemNoComma = true; + parseProperty(); + } else { + throw lexer.error("Unexpected JSON syntax"); + } + break; + case Array : + lexer.next(); + lexer.getStates().push(new State(itemName+"[]", false)); + parseProperty(); + break; + case Eof : + throw lexer.error("JSON Syntax Error - attempt to read past end of json stream"); + default: + throw lexer.error("not done yet (a): "+itemType.toString()); + } + } + + private void parseProperty() throws IOException { + if (lexer.getStates().peek().isProp()) { + itemUnquoted = lexer.isUnquoted(); + itemName = lexer.consume(TokenType.String); + itemValue = null; + lexer.consume(TokenType.Colon); + } + startProperty = lexer.getLastLocationAWS().copy(); + endProperty = lexer.getLocation().copy(); + valueUnquoted = lexer.isUnquoted(); + switch (lexer.getType()) { + case Null : + itemType = ItemType.Null; + itemValue = lexer.getValue(); + lexer.next(); + break; + case String : + itemType = ItemType.String; + itemValue = lexer.getValue(); + lexer.next(); + break; + case Boolean : + itemType = ItemType.Boolean; + itemValue = lexer.getValue(); + lexer.next(); + break; + case Number : + itemType = ItemType.Number; + itemValue = lexer.getValue(); + lexer.next(); + break; + case Open : + itemType = ItemType.Object; + break; + case OpenArray : + itemType = ItemType.Array; + break; + case CloseArray : + itemType = ItemType.End; + break; + // case Close, , case Colon, case Comma, case OpenArray, ! + default: + throw lexer.error("not done yet (b): "+lexer.getType().toString()); + } + } + + private String write(JsonElement element, boolean pretty) { + StringBuilder b = new StringBuilder(); + if (pretty && element.hasComments()) { + writeComments(b, element.getComments(), 0); + } + write(b, element, pretty, 0); + if (pretty) { + b.append("\n"); + } + return b.toString(); + } + + private void writeComments(StringBuilder b, List comments, int indent) { + for (String s : comments) { + b.append("// "); + b.append(s); + b.append("\n"); + b.append(Utilities.padLeft("", ' ', indent)); + } + } + + private void write(StringBuilder b, JsonElement e, boolean pretty, int indent) { + switch (e.elementType()) { + case ARRAY: + JsonArray arr = (JsonArray) e; + b.append("["); + boolean first = true; + boolean complex = arr.size() > 6; // arbitrary cut off + if (!complex) { + int length = 0; + for (JsonElement i : arr.getItems()) { + if (i instanceof JsonPrimitive) { + length = length + i.toString().length(); + } + if (i.elementType() == JsonElementType.ARRAY || i.elementType() == JsonElementType.OBJECT + || i.hasComments()) { // 20 is a somewhat arbitrary cut off + complex = true; + } + } + if (length > 60) { + complex = true; + } + } + for (JsonElement i : arr.getItems()) { + if (first) first = false; else b.append(pretty && !complex ? ", " : ","); + if (pretty && complex) { + b.append("\n"); + b.append(Utilities.padLeft("", ' ', indent+2)); + if (i.hasComments()) { + writeComments(b, i.getComments(), indent+2); + } + } + write(b, i, pretty && complex, indent+2); + } + if (pretty && complex) { + b.append("\n"); + b.append(Utilities.padLeft("", ' ', indent)); + } + b.append("]"); + break; + case BOOLEAN: + b.append(((JsonBoolean) e).getValue()); + break; + case NULL: + b.append(((JsonNull) e).getValue()); + break; + case NUMBER: + b.append(((JsonNumber) e).getValue()); + break; + case OBJECT: + b.append("{"); + first = true; + for (JsonProperty p : ((JsonObject) e).getProperties()) { + if (first) first = false; else b.append(","); + if (pretty) { + b.append("\n"); + b.append(Utilities.padLeft("", ' ', indent+2)); + if (p.getValue().hasComments()) { + writeComments(b, p.getValue().getComments(), indent+2); + } + } + b.append("\""); + b.append(p.getName()); + b.append(pretty ? "\" : " : "\":"); + write(b, p.getValue(), pretty, indent+2); + } + if (pretty) { + b.append("\n"); + b.append(Utilities.padLeft("", ' ', indent)); + } + b.append("}"); + break; + case STRING: + b.append("\""); + b.append(Utilities.escapeJson(((JsonString) e).getValue())); + b.append("\""); + break; + default: + throw new Error("Can't get here"); + } + } + + private static byte[] fetch(String source) throws IOException { + SimpleHTTPClient fetcher = new SimpleHTTPClient(); + HTTPResult res = fetcher.get(source+"?nocache=" + System.currentTimeMillis()); + res.checkThrowException(); + return res.getContent(); + } +} diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/json/JsonParserTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/json/JsonParserTests.java new file mode 100644 index 000000000..d7ff1a9a0 --- /dev/null +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/json/JsonParserTests.java @@ -0,0 +1,587 @@ +package org.hl7.fhir.utilities.json; + +import java.io.IOException; + +import org.hl7.fhir.utilities.json.model.JsonArray; +import org.hl7.fhir.utilities.json.model.JsonBoolean; +import org.hl7.fhir.utilities.json.model.JsonElement; +import org.hl7.fhir.utilities.json.model.JsonElementType; +import org.hl7.fhir.utilities.json.model.JsonNull; +import org.hl7.fhir.utilities.json.model.JsonNumber; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.json.model.JsonProperty; +import org.hl7.fhir.utilities.json.model.JsonString; +import org.hl7.fhir.utilities.json.parser.JsonParser; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class JsonParserTests { + + @Test + public void testComments1() throws IOException, JsonException { + Assertions.assertThrows(IOException.class, () -> JsonParser.parseObject("{\n // some comment \n \"n1\" : \"v1\"\n}\n", false)); + } + + @Test + public void testComments2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\n // some comment \n \"n1\" : \"v1\"\n}\n", true); + Assertions.assertEquals(0, obj.getComments().size()); + JsonString c = obj.getStr("n1"); + Assertions.assertEquals(1, c.getComments().size()); + Assertions.assertEquals("some comment", c.getComments().get(0)); + Assertions.assertEquals("{\"n1\":\"v1\"}", JsonParser.compose(obj, false)); + Assertions.assertEquals("{\n // some comment\n \"n1\" : \"v1\"\n}\n", JsonParser.compose(obj, true)); + } + + @Test + public void testComments3() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("// some comment\n{\n \"n1\" : \"v1\"\n}\n", true); + Assertions.assertEquals(1, obj.getComments().size()); + Assertions.assertEquals("some comment", obj.getComments().get(0)); + JsonString c = obj.getStr("n1"); + Assertions.assertEquals(0, c.getComments().size()); + Assertions.assertEquals("{\"n1\":\"v1\"}", JsonParser.compose(obj, false)); + Assertions.assertEquals("// some comment\n{\n \"n1\" : \"v1\"\n}\n", JsonParser.compose(obj, true)); + } + + @Test + public void testEmpty1() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{}"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(3, obj.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testEmpty2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + } + + + private void checkSubstring(String src, JsonElement obj, String tgt) { + String s = src.substring(obj.getStart().getCol()-1, obj.getEnd().getCol()-1); + Assertions.assertEquals(tgt, s); + } + + @Test + public void testString1() throws IOException, JsonException { + String src = "{\"name\":\"value\"}"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(17, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + checkSubstring(src, obj, src); + JsonProperty p = obj.getProperties().get(0); + Assertions.assertNotNull(p); + JsonElement e = obj.get("name"); + Assertions.assertSame(p.getValue(), e); + JsonString j = (JsonString) e; + checkSubstring(src, j, "\"value\""); + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(16, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testString2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n \"name\" : \"value\"\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonElement e = obj.get("name"); + JsonString j = (JsonString) e; + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(19, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testString3() throws IOException, JsonException { + String src = "{\"name\":\"value\",\"n1\":\"v1\"}"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(27, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(2, obj.getProperties().size(), "prop count is wrong"); + checkSubstring(src, obj, src); + + JsonProperty p = obj.getProperties().get(0); + Assertions.assertNotNull(p); + JsonElement e = obj.get("name"); + Assertions.assertSame(p.getValue(), e); + JsonString j = (JsonString) e; + checkSubstring(src, j, "\"value\""); + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(16, j.getEnd().getCol(), "end col is wrong"); + + p = obj.getProperties().get(1); + Assertions.assertNotNull(p); + e = obj.get("n1"); + j = (JsonString) e; + Assertions.assertSame(p.getValue(), e); + checkSubstring(src, j, "\"v1\""); + s = obj.str("n1"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(22, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(26, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testString4() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n \"name\" : \"value\"\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonElement e = obj.get("name"); + JsonString j = (JsonString) e; + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(19, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testStringE1() throws IOException, JsonException { + String src = "{\"name\":\"\"}"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(12, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + checkSubstring(src, obj, src); + JsonProperty p = obj.getProperties().get(0); + Assertions.assertNotNull(p); + JsonElement e = obj.get("name"); + JsonString j = (JsonString) e; + checkSubstring(src, j, "\"\""); + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(11, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testStringE2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n \"name\" : \"\"\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonElement e = obj.get("name"); + JsonString j = (JsonString) e; + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(14, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testInt1() throws IOException, JsonException { + String src = "{\"name\":1}"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(11, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + checkSubstring(src, obj, src); + JsonElement e = obj.get("name"); + JsonNumber j = (JsonNumber) e; + checkSubstring(src, j, "1"); + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(10, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testInt2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n \"name\" : 1\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonProperty p = obj.getProperties().get(0); + Assertions.assertNotNull(p); + JsonElement e = obj.get("name"); + JsonNumber j = (JsonNumber) e; + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(13, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testBool1() throws IOException, JsonException { + String src = "{\"name\":true}"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(14, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + checkSubstring(src, obj, src); + JsonElement e = obj.get("name"); + JsonBoolean j = (JsonBoolean) e; + checkSubstring(src, j, "true"); + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(13, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testBool2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n \"name\" : false\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonProperty p = obj.getProperties().get(0); + Assertions.assertNotNull(p); + JsonElement e = obj.get("name"); + JsonBoolean j = (JsonBoolean) e; + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(17, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testNull1() throws IOException, JsonException { + String src = "{\"name\":null}"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(14, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + checkSubstring(src, obj, src); + JsonElement e = obj.get("name"); + JsonNull j = (JsonNull) e; + checkSubstring(src, j, "null"); + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(13, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testNull2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n \"name\" : null\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonProperty p = obj.getProperties().get(0); + Assertions.assertNotNull(p); + JsonElement e = obj.get("name"); + JsonNull j = (JsonNull) e; + String s = obj.str("name"); + Assertions.assertEquals(j.getValue(), s, "string prop value"); + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(16, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testObject1() throws IOException, JsonException { + String src = "{\"name\":{\"n1\":\"v1\"}}"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(21, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + checkSubstring(src, obj, src); + JsonElement e = obj.get("name"); + JsonObject j = (JsonObject) e; + checkSubstring(src, j, "{\"n1\":\"v1\"}"); + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(20, j.getEnd().getCol(), "end col is wrong"); + + JsonElement e1 = j.get("n1"); + JsonString j1 = (JsonString) e1; + checkSubstring(src, j1, "\"v1\""); + Assertions.assertEquals("v1", j1.getValue()); + Assertions.assertEquals(1, j1.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(15, j1.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j1.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(19, j1.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testObject2() throws IOException, JsonException { + String src = "{\r\n \"name\" : {\r\n \"n1\":\"v1\"\r\n }\r\n}\r\n"; + JsonObject obj = JsonParser.parseObject(src); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(5, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonElement e = obj.get("name"); + JsonObject j = (JsonObject) e; + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(4, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(4, j.getEnd().getCol(), "end col is wrong"); + + JsonElement e1 = j.get("n1"); + JsonString j1 = (JsonString) e1; + Assertions.assertEquals("v1", j1.getValue()); + Assertions.assertEquals(3, j1.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(10, j1.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, j1.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(14, j1.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testArrEmpty1() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\"name\":[]}"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(12, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonElement e = obj.get("name"); + JsonArray j = (JsonArray) e; + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(11, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testArrEmpty2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\r\n \"name\" : [ ]\r\n}\r\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + JsonElement e = obj.get("name"); + JsonArray j = (JsonArray) e; + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(2, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(15, j.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testArr() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\"name\":[\"v\",{\"n\":\"v1\"}]}"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(26, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonElement e = obj.get("name"); + JsonArray j = (JsonArray) e; + Assertions.assertEquals(1, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(9, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(25, j.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(2, j.size()); + + JsonElement e1 = j.getItems().get(0); + JsonString j1 = (JsonString) e1; + Assertions.assertEquals(1, j1.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(10, j1.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j1.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(13, j1.getEnd().getCol(), "end col is wrong"); + JsonElement e2 = j.getItems().get(1); + JsonObject j2 = (JsonObject) e2; + Assertions.assertEquals(1, j2.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(14, j2.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(1, j2.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(24, j2.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testArr2() throws IOException, JsonException { + JsonObject obj = JsonParser.parseObject("{\n \"name\" : [\n \"v\",\n {\n \"n\" : \"v1\"\n }\n ]\n}\n"); + Assertions.assertEquals(1, obj.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(1, obj.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(8, obj.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(2, obj.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(1, obj.getProperties().size(), "prop count is wrong"); + JsonElement e = obj.get("name"); + JsonArray j = (JsonArray) e; + Assertions.assertEquals(2, j.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(12, j.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(7, j.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(4, j.getEnd().getCol(), "end col is wrong"); + Assertions.assertEquals(2, j.size()); + + JsonElement e1 = j.getItems().get(0); + JsonString j1 = (JsonString) e1; + Assertions.assertEquals(3, j1.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(5, j1.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(3, j1.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(8, j1.getEnd().getCol(), "end col is wrong"); + JsonElement e2 = j.getItems().get(1); + JsonObject j2 = (JsonObject) e2; + Assertions.assertEquals(4, j2.getStart().getLine(), "start line is wrong"); + Assertions.assertEquals(5, j2.getStart().getCol(), "start col is wrong"); + Assertions.assertEquals(6, j2.getEnd().getLine(), "end line is wrong"); + Assertions.assertEquals(6, j2.getEnd().getCol(), "end col is wrong"); + } + + @Test + public void testElementBool() throws IOException, JsonException { + JsonElement e = JsonParser.parse("true"); + Assertions.assertEquals(JsonElementType.BOOLEAN, e.elementType()); + Assertions.assertEquals(true, ((JsonBoolean) e).isValue()); + e = JsonParser.parse("// comment\nfalse", true); + Assertions.assertEquals(JsonElementType.BOOLEAN, e.elementType()); + Assertions.assertEquals(false, ((JsonBoolean) e).isValue()); + Assertions.assertEquals("false", JsonParser.compose(e)); + Assertions.assertEquals("// comment\nfalse\n", JsonParser.compose(e, true)); + } + + @Test + public void testElementNumber() throws IOException, JsonException { + JsonElement e = JsonParser.parse("1"); + Assertions.assertEquals(JsonElementType.NUMBER, e.elementType()); + Assertions.assertEquals("1", ((JsonNumber) e).getValue()); + e = JsonParser.parse("// comment \n-1.2e10", true); + Assertions.assertEquals(JsonElementType.NUMBER, e.elementType()); + Assertions.assertEquals("-1.2e10", ((JsonNumber) e).getValue()); + Assertions.assertEquals("-1.2e10", JsonParser.compose(e)); + Assertions.assertEquals("// comment\n-1.2e10\n", JsonParser.compose(e, true)); + } + + @Test + public void testElementString() throws IOException, JsonException { + JsonElement e = JsonParser.parse("\"str\\ning\""); + Assertions.assertEquals(JsonElementType.STRING, e.elementType()); + Assertions.assertEquals("str\ning", ((JsonString) e).getValue()); + Assertions.assertEquals("\"str\\ning\"", JsonParser.compose(e)); + Assertions.assertEquals("\"str\\ning\"\n", JsonParser.compose(e, true)); + e = JsonParser.parse("// comment\n\"\"", true); + Assertions.assertEquals(JsonElementType.STRING, e.elementType()); + Assertions.assertEquals("", ((JsonString) e).getValue()); + } + + @Test + public void testElementNull() throws IOException, JsonException { + JsonElement e = JsonParser.parse("null"); + Assertions.assertEquals(JsonElementType.NULL, e.elementType()); + Assertions.assertEquals("null", JsonParser.compose(e)); + Assertions.assertEquals("null\n", JsonParser.compose(e, true)); + } + + @Test + public void testElementArray() throws IOException, JsonException { + JsonElement e = JsonParser.parse("[\"test\", null, true]"); + Assertions.assertEquals(JsonElementType.ARRAY, e.elementType()); + Assertions.assertEquals(3, ((JsonArray) e).size()); + Assertions.assertEquals("[\"test\",null,true]", JsonParser.compose(e)); + Assertions.assertEquals("[\"test\", null, true]\n", JsonParser.compose(e, true)); + e = JsonParser.parse("// comment\n[]", true); + Assertions.assertEquals(JsonElementType.ARRAY, e.elementType()); + Assertions.assertEquals(0, ((JsonArray) e).size()); + } + + @Test + public void testDuplicates1() throws IOException, JsonException { + Assertions.assertThrows(IOException.class, () -> JsonParser.parseObject("{ \"n\" : 1, \"n\" : 2 }", false, false)); + } + + @Test + public void testDuplicates2() throws IOException, JsonException { + JsonObject e = JsonParser.parseObject("{ \"n\" : 1, \"n\" : 2 }", false, true); + Assertions.assertEquals(2, e.getProperties().size()); + Assertions.assertEquals(2, e.getInteger("n")); + Assertions.assertEquals("{\"n\":1,\"n\":2}", JsonParser.compose(e)); + } + + @Test + public void testNoComma1() throws IOException, JsonException { + Assertions.assertThrows(IOException.class, () -> JsonParser.parseObject("{ \"n1\" : 1 \"n2\" : 2 }", false)); + } + + @Test + public void testNoComma2() throws IOException, JsonException { + JsonObject e = JsonParser.parseObject("{ \"n1\" : 1 \"n2\" : 2 }", true); + Assertions.assertEquals(2, e.getProperties().size()); + Assertions.assertEquals(false, e.getProperties().get(0).isNoComma()); + Assertions.assertEquals(true, e.getProperties().get(1).isNoComma()); + } + + @Test + public void testNoCommaInArr1() throws IOException, JsonException { + Assertions.assertThrows(IOException.class, () -> JsonParser.parseObject("[1 2]", false)); + } + + @Test + public void testNoCommainArr2() throws IOException, JsonException { + JsonArray e = (JsonArray) JsonParser.parse("[1 2]", true); + Assertions.assertEquals(2, e.size()); + Assertions.assertEquals(false, e.isNoComma(0)); + Assertions.assertEquals(true, e.isNoComma(1)); + } + + + @Test + public void testUnquoted1() throws IOException, JsonException { + Assertions.assertThrows(IOException.class, () -> JsonParser.parseObject("{ this: that, \"the\" : other}", false)); + } + + @Test + public void testUnquoted2() throws IOException, JsonException { + JsonObject e = JsonParser.parseObject("{ this: that, \"the\" : other}", true); + Assertions.assertEquals(2, e.getProperties().size()); + Assertions.assertEquals(true, e.getProperties().get(0).isUnquotedName()); + Assertions.assertEquals(true, e.getProperties().get(0).isUnquotedValue()); + Assertions.assertEquals(false, e.getProperties().get(1).isUnquotedName()); + Assertions.assertEquals(true, e.getProperties().get(1).isUnquotedValue()); + } +} diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/JsonParserTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/JsonTrackingParserTests.java similarity index 85% rename from org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/JsonParserTests.java rename to org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/JsonTrackingParserTests.java index 5b4c1dbc0..8122218fc 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/JsonParserTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/JsonTrackingParserTests.java @@ -5,7 +5,7 @@ import java.io.IOException; import org.hl7.fhir.utilities.json.JsonTrackingParser; import org.junit.jupiter.api.Test; -public class JsonParserTests { +public class JsonTrackingParserTests { @Test public void test() throws IOException {