update JSON parser used by validator to JSON5 parser

This commit is contained in:
Grahame Grieve 2022-11-26 18:24:29 -03:00
parent 363a95bb8a
commit 53fb08ab19
21 changed files with 432 additions and 241 deletions

View File

@ -37,6 +37,7 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
@ -54,6 +55,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonCreator;
import org.hl7.fhir.r5.formats.JsonCreatorCanonical;
import org.hl7.fhir.r5.formats.JsonCreatorGson;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -64,24 +66,22 @@ import org.hl7.fhir.utilities.StringPair;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData;
import org.hl7.fhir.utilities.json.JsonUtilities;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonComment;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonNull;
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.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
public class JsonParser extends ParserBase {
private JsonCreator json;
private Map<JsonElement, LocationData> map;
private boolean allowComments;
private ProfileUtilities profileUtilities;
@ -100,7 +100,7 @@ public class JsonParser extends ParserBase {
}
public Element parse(String source, String type) throws Exception {
JsonObject obj = (JsonObject) new com.google.gson.JsonParser().parse(source);
JsonObject obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true);
String path = "/"+type;
StructureDefinition sd = getDefinition(-1, -1, type);
if (sd == null)
@ -108,7 +108,7 @@ public class JsonParser extends ParserBase {
Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities));
result.setPath(type);
checkObject(obj, path);
checkObject(obj, result, path);
result.setType(type);
parseChildren(path, obj, result, true);
result.numberChildren();
@ -120,37 +120,25 @@ public class JsonParser extends ParserBase {
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRException {
// if we're parsing at this point, then we're going to use the custom parser
List<NamedElement> res = new ArrayList<>();
map = new IdentityHashMap<JsonElement, LocationData>();
String source = TextFile.streamToString(stream);
JsonObject obj = null;
if (policy == ValidationPolicy.EVERYTHING) {
JsonObject obj = null;
try {
obj = JsonTrackingParser.parse(source, map, false, allowComments);
obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true);
} catch (Exception e) {
logError(ValidationMessage.NO_RULE_DATE, -1, -1,context.formatMessage(I18nConstants.DOCUMENT), IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL);
return null;
}
assert (map.containsKey(obj));
Element e = parse(obj);
if (e != null) {
res.add(new NamedElement(null, e));
}
} else {
JsonObject obj = JsonTrackingParser.parse(source, null); // (JsonObject) new com.google.gson.JsonParser().parse(source);
// assert (map.containsKey(obj));
Element e = parse(obj);
if (e != null) {
res.add(new NamedElement(null, e));
}
obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true);
}
Element e = parse(obj);
if (e != null) {
res.add(new NamedElement(null, e));
}
return res;
}
public Element parse(JsonObject object, Map<JsonElement, LocationData> map) throws FHIRException {
this.map = map;
return parse(object);
}
public Element parse(JsonObject object) throws FHIRException {
StructureDefinition sd = getLogical();
String name;
@ -159,8 +147,11 @@ public class JsonParser extends ParserBase {
if (rt == null) {
logError(ValidationMessage.NO_RULE_DATE, line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
return null;
} else if (!rt.isJsonString()) {
logError("2022-11-26", line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL);
return null;
} else {
name = rt.getAsString();
name = rt.asString();
sd = getDefinition(line(object), col(object), name);
if (sd == null) {
@ -172,7 +163,7 @@ public class JsonParser extends ParserBase {
}
String path = name;
baseElement = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities));
checkObject(object, path);
checkObject(object, baseElement, path);
baseElement.markLocation(line(object), col(object));
baseElement.setType(name);
baseElement.setPath(baseElement.fhirType());
@ -181,17 +172,26 @@ public class JsonParser extends ParserBase {
return baseElement;
}
private void checkObject(JsonObject object, String path) throws FHIRFormatError {
private void checkObject(JsonObject object, Element b, String path) {
checkComments(object, b, path);
if (policy == ValidationPolicy.EVERYTHING) {
boolean found = false;
for (Entry<String, JsonElement> e : object.entrySet()) {
// if (!e.getKey().equals("fhir_comments")) {
found = true;
break;
// }
}
if (!found)
if (object.getProperties().size() == 0) {
logError(ValidationMessage.NO_RULE_DATE, line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
}
}
}
private void checkComments(JsonElement element, Element b, String path) throws FHIRFormatError {
if (element.hasComments()) {
if (allowComments) {
for (JsonComment c : element.getComments()) {
b.getFormatCommentsPre().add(c.getContent());
}
} else {
for (JsonComment c : element.getComments()) {
logError("2022-11-26", c.getStart().getLine(), c.getStart().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMENTS_NOT_ALLOWED), IssueSeverity.ERROR);
}
}
}
}
@ -199,43 +199,64 @@ public class JsonParser extends ParserBase {
reapComments(object, element);
List<Property> properties = element.getProperty().getChildProperties(element.getName(), null);
Set<String> processed = new HashSet<String>();
if (hasResourceType)
if (hasResourceType) {
processed.add("resourceType");
}
Map<String, JsonProperty> recognisedChildren = new HashMap<>();
Set<String> unique = new HashSet<>();
for (JsonProperty p : object.getProperties()) {
if (p.isUnquotedName()) {
logError("2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_NO_QUOTES, p.getName()), IssueSeverity.ERROR);
}
if (p.isNoComma()) {
logError("2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_MISSING), IssueSeverity.ERROR);
}
if (unique.contains(p.getName())) {
logError("2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY, p.getName()), IssueSeverity.ERROR);
} else {
unique.add(p.getName());
recognisedChildren.put(p.getName(), p);
}
}
// note that we do not trouble ourselves to maintain the wire format order here - we don't even know what it was anyway
// first pass: process the properties
for (Property property : properties) {
parseChildItem(path, object, element, processed, property);
parseChildItem(path, recognisedChildren, element, processed, property);
}
// second pass: check for things not processed
if (policy != ValidationPolicy.NONE) {
for (Entry<String, JsonElement> e : object.entrySet()) {
for (Entry<String, JsonProperty> e : recognisedChildren.entrySet()) {
if (!processed.contains(e.getKey())) {
StructureDefinition sd = element.getProperty().isLogical() ? new ContextUtilities(context).fetchByJsonName(e.getKey()) : null;
if (sd != null) {
Property property = new Property(context, sd.getSnapshot().getElementFirstRep(), sd, element.getProperty().getUtils());
parseChildItem(path, object, element, null, property);
parseChildItem(path, recognisedChildren, element, null, property);
} else {
logError(ValidationMessage.NO_RULE_DATE, line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getKey()), IssueSeverity.ERROR);
logError(ValidationMessage.NO_RULE_DATE, line(e.getValue().getValue()), col(e.getValue().getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getKey()), IssueSeverity.ERROR);
}
}
}
}
if (object.isExtraComma()) {
logError("2022-11-26", object.getEnd().getLine(), object.getEnd().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
}
}
public void parseChildItem(String path, JsonObject object, Element context, Set<String> processed, Property property) {
public void parseChildItem(String path, Map<String, JsonProperty> children, Element context, Set<String> processed, Property property) {
if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) {
if (property.isJsonPrimitiveChoice()) {
if (object.has(property.getJsonName())) {
JsonElement je = object.get(property.getJsonName());
if (children.containsKey(property.getJsonName())) {
JsonElement je = children.get(property.getJsonName()).getValue();
if (processed != null) processed.add(property.getJsonName());
String type = getTypeFromJsonType(je);
if (type == null) {
logError(ValidationMessage.NO_RULE_DATE, line(je), col(je), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE, describeType(je), property.getName(), property.typeSummary()), IssueSeverity.ERROR);
} else if (property.hasType(type)) {
Property np = new Property(property.getContext(), property.getDefinition(), property.getStructure(), property.getUtils(), type);
parseChildPrimitive(object, context, processed, np, path, property.getName());
parseChildPrimitive(children, context, processed, np, path, property.getName());
} else {
logError(ValidationMessage.NO_RULE_DATE, line(je), col(je), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE_WRONG, describeType(je), property.getName(), type, property.typeSummary()), IssueSeverity.ERROR);
}
@ -243,31 +264,31 @@ public class JsonParser extends ParserBase {
} else {
for (TypeRefComponent type : property.getDefinition().getType()) {
String eName = property.getJsonName().substring(0, property.getName().length()-3) + Utilities.capitalize(type.getWorkingCode());
if (!isPrimitive(type.getWorkingCode()) && object.has(eName)) {
parseChildComplex(path, object, context, processed, property, eName);
if (!isPrimitive(type.getWorkingCode()) && children.containsKey(eName)) {
parseChildComplex(path, children, context, processed, property, eName);
break;
} else if (isPrimitive(type.getWorkingCode()) && (object.has(eName) || object.has("_"+eName))) {
parseChildPrimitive(object, context, processed, property, path, eName);
} else if (isPrimitive(type.getWorkingCode()) && (children.containsKey(eName) || children.containsKey("_"+eName))) {
parseChildPrimitive(children, context, processed, property, path, eName);
break;
}
}
}
} else if (property.isPrimitive(property.getType(null))) {
parseChildPrimitive(object, context, processed, property, path, property.getJsonName());
} else if (object.has(property.getJsonName())) {
parseChildComplex(path, object, context, processed, property, property.getJsonName());
parseChildPrimitive(children, context, processed, property, path, property.getJsonName());
} else if (children.containsKey(property.getJsonName())) {
parseChildComplex(path, children, context, processed, property, property.getJsonName());
}
}
private String getTypeFromJsonType(JsonElement je) {
if (je.isJsonPrimitive()) {
JsonPrimitive p = je.getAsJsonPrimitive();
if (p.isString()) {
JsonPrimitive p = je.asJsonPrimitive();
if (p.isJsonString()) {
return "string";
} else if (p.isBoolean()) {
} else if (p.isJsonBoolean()) {
return "boolean";
} else {
String s = p.getAsString();
String s = p.asString();
if (Utilities.isInteger(s)) {
return "integer";
} else {
@ -279,15 +300,19 @@ public class JsonParser extends ParserBase {
}
}
private void parseChildComplex(String path, JsonObject object, Element element, Set<String> processed, Property property, String name) throws FHIRException {
private void parseChildComplex(String path, Map<String, JsonProperty> children, Element element, Set<String> processed, Property property, String name) throws FHIRException {
if (processed != null) {
processed.add(name);
}
String npath = path+"."+property.getName();
String fpath = element.getPath()+"."+property.getName();
JsonElement e = object.get(name);
JsonProperty p = children.get(name);
JsonElement e = p == null ? null : p.getValue();
if (property.isList() && !property.isJsonKeyArray() && (e instanceof JsonArray)) {
JsonArray arr = (JsonArray) e;
if (arr.isExtraComma()) {
logError("2022-11-26", arr.getEnd().getLine(), arr.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
}
if (arr.size() == 0) {
if (property.canBeEmpty()) {
// nothing
@ -320,8 +345,18 @@ public class JsonParser extends ParserBase {
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(e)), IssueSeverity.ERROR);
} else {
JsonObject o = (JsonObject) e;
if (o.isExtraComma()) {
logError("2022-11-26", o.getEnd().getLine(), o.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
}
int i = 0;
for (Entry<String, JsonElement> pv : o.entrySet()) {
Set<String> names = new HashSet<>();
for (JsonProperty pv : o.getProperties()) {
if (names.contains(pv.getName())) {
logError("2022-11-26", line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, pv.getName()), IssueSeverity.ERROR);
} else {
names.add(pv.getName());
}
// create an array entry
String npathArr = path+"."+property.getName()+"["+i+"]";
String fpathArr = element.getPath()+"."+property.getName()+"["+i+"]";
@ -332,9 +367,10 @@ public class JsonParser extends ParserBase {
// handle the key
String fpathKey = fpathArr+"."+propK.getName();
Element nKey = new Element(code, propK).markLocation(line(pv.getValue()), col(pv.getValue()));
checkComments(pv.getValue(), n, fpathArr);
nKey.setPath(fpathKey);
n.getChildren().add(nKey);
nKey.setValue(pv.getKey());
nKey.setValue(pv.getName());
boolean ok = true;
@ -418,7 +454,7 @@ public class JsonParser extends ParserBase {
JsonObject child = (JsonObject) e;
Element n = new Element(name, property).markLocation(line(child), col(child));
n.setPath(fpath);
checkObject(child, npath);
checkObject(child, n, npath);
element.getChildren().add(n);
if (property.isResource()) {
parseResource(npath, child, n, property);
@ -429,6 +465,7 @@ public class JsonParser extends ParserBase {
// we create an element marked as a null element so we know something was present
JsonNull child = (JsonNull) e;
Element n = new Element(name, property).markLocation(line(child), col(child));
checkComments(child, n, fpath);
n.setPath(fpath);
element.getChildren().add(n);
n.setNull(true);
@ -455,57 +492,51 @@ public class JsonParser extends ParserBase {
}
private String describeType(JsonElement e) {
if (e instanceof JsonArray) {
return "array";
}
if (e instanceof JsonObject) {
return "object";
}
if (e instanceof JsonNull) {
return "null";
}
if (e instanceof JsonPrimitive) {
JsonPrimitive p = (JsonPrimitive) e;
if (p.isString()) {
return "string";
} else if (p.isBoolean()) {
return "boolean";
} else if (p.isNumber()) {
return "number";
}
}
return "??";
return e.type().toName();
}
private void parseChildPrimitive(JsonObject object, Element element, Set<String> processed, Property property, String path, String name) throws FHIRException {
private void parseChildPrimitive(Map<String, JsonProperty> children, Element element, Set<String> processed, Property property, String path, String name) throws FHIRException {
String npath = path+"."+property.getName();
String fpath = element.getPath()+"."+property.getName();
processed.add(name);
processed.add("_"+name);
JsonElement main = object.has(name) ? object.get(name) : null;
JsonElement fork = object.has("_"+name) ? object.get("_"+name) : null;
JsonProperty main = children.containsKey(name) ? children.get(name) : null;
JsonProperty fork = children.containsKey("_"+name) ? children.get("_"+name) : null;
if (main != null && main.getValue().isJsonString() && main.isUnquotedValue()) {
logError("2022-11-26", line(main.getValue()), col(main.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_VALUE_NO_QUOTES, main.getName(), main.getValue().asString()), IssueSeverity.ERROR);
}
if (main != null || fork != null) {
if (property.isList()) {
boolean ok = true;
if (!(main == null || main instanceof JsonArray)) {
logError(ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR);
if (!(main == null || main.getValue() instanceof JsonArray)) {
logError(ValidationMessage.NO_RULE_DATE, line(main.getValue()), col(main.getValue()), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main.getValue()), name, path), IssueSeverity.ERROR);
ok = false;
}
if (!(fork == null || fork instanceof JsonArray)) {
logError(ValidationMessage.NO_RULE_DATE, line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_BASE_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR);
if (!(fork == null || fork.getValue() instanceof JsonArray)) {
logError(ValidationMessage.NO_RULE_DATE, line(fork.getValue()), col(fork.getValue()), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_BASE_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main.getValue()), name, path), IssueSeverity.ERROR);
ok = false;
}
if (ok) {
JsonArray arr1 = (JsonArray) main;
JsonArray arr2 = (JsonArray) fork;
JsonArray arr1 = (JsonArray) (main == null ? null : main.getValue());
JsonArray arr2 = (JsonArray) (fork == null ? null : fork.getValue());
if (arr1 != null && arr1.isExtraComma()) {
logError("2022-11-26", arr1.getEnd().getLine(), arr1.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
}
if (arr2 != null && arr2.isExtraComma()) {
logError("2022-11-26", arr2.getEnd().getLine(), arr2.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
}
for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) {
JsonElement m = arrI(arr1, i);
JsonElement f = arrI(arr2, i);
if (m != null && m.isJsonString() && arr1.isUnquoted(i)) {
logError("2022-11-26", line(m), col(m), path+"."+name+"["+i+"]", IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_VALUE_NO_QUOTES, "item", m.asString()), IssueSeverity.ERROR);
}
parseChildPrimitiveInstance(element, property, name, npath, fpath, m, f);
}
}
} else {
parseChildPrimitiveInstance(element, property, name, npath, fpath, main, fork);
parseChildPrimitiveInstance(element, property, name, npath, fpath, main == null ? null : main.getValue(), fork == null ? null : fork.getValue());
}
}
}
@ -519,23 +550,24 @@ public class JsonParser extends ParserBase {
}
private void parseChildPrimitiveInstance(Element element, Property property, String name, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException {
if (main != null && !(main instanceof JsonPrimitive))
if (main != null && !(main.isJsonBoolean() || main.isJsonNumber() || main.isJsonString())) {
logError(ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(
I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR);
else if (fork != null && !(fork instanceof JsonObject))
} else if (fork != null && !(fork instanceof JsonObject)) {
logError(ValidationMessage.NO_RULE_DATE, line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(fork), name, npath), IssueSeverity.ERROR);
else {
} else {
Element n = new Element(name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork));
if (main != null) {
checkComments(main, n, npath);
}
if (fork != null) {
checkComments(fork, n, npath);
}
n.setPath(fpath);
element.getChildren().add(n);
if (main != null) {
JsonPrimitive p = (JsonPrimitive) main;
if (p.isNumber() && p.getAsNumber() instanceof JsonTrackingParser.PresentedBigDecimal) {
String rawValue = ((JsonTrackingParser.PresentedBigDecimal) p.getAsNumber()).getPresentation();
n.setValue(property.hasImpliedPrefix() ? property.getImpliedPrefix()+rawValue : rawValue);
} else {
n.setValue(property.hasImpliedPrefix() ? property.getImpliedPrefix()+p.getAsString() : p.getAsString());
}
n.setValue(property.hasImpliedPrefix() ? property.getImpliedPrefix()+p.asString() : p.asString());
if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) {
try {
XhtmlParser xhtml = new XhtmlParser();
@ -552,19 +584,20 @@ public class JsonParser extends ParserBase {
if (policy == ValidationPolicy.EVERYTHING) {
// now we cross-check the primitive format against the stated type
if (Utilities.existsInList(n.getType(), "boolean")) {
if (!p.isBoolean()) {
if (!p.isJsonBoolean()) {
logError(ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_BOOLEAN), IssueSeverity.ERROR);
}
} else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) {
if (!p.isNumber())
if (!p.isJsonNumber())
logError(ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_NUMBER), IssueSeverity.ERROR);
} else if (!p.isString())
} else if (!p.isJsonString()) {
logError(ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_STRING), IssueSeverity.ERROR);
}
}
}
if (fork != null) {
JsonObject child = (JsonObject) fork;
checkObject(child, npath);
checkObject(child, n, npath);
parseChildren(npath, child, n, false);
}
}
@ -575,8 +608,10 @@ public class JsonParser extends ParserBase {
JsonElement rt = res.get("resourceType");
if (rt == null) {
logError(ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
} else if (!rt.isJsonString()) {
logError("2022-11-26", line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL);
} else {
String name = rt.getAsString();
String name = rt.asString();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null));
if (sd == null) {
logError(ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);
@ -586,29 +621,22 @@ public class JsonParser extends ParserBase {
parseChildren(npath, res, parent, true);
}
}
if (res.isExtraComma()) {
logError("2022-11-26", res.getEnd().getLine(), res.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
}
}
private void reapComments(JsonObject object, Element context) {
if (object.has("fhir_comments")) {
JsonArray arr = object.getAsJsonArray("fhir_comments");
for (JsonElement e : arr) {
context.getComments().add(e.getAsString());
}
}
// todo
}
private int line(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getLine();
return e.getStart().getLine();
}
private int col(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getCol();
return e.getEnd().getCol();
}

View File

@ -21,18 +21,16 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData;
import org.hl7.fhir.utilities.json.JsonUtilities;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonElementType;
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.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* this class is actually a smart health cards validator.
* It's going to parse the JWT and assume that it contains
@ -51,7 +49,6 @@ import com.google.gson.JsonPrimitive;
public class SHCParser extends ParserBase {
private JsonParser jsonParser;
private Map<JsonElement, LocationData> map;
private List<String> types = new ArrayList<>();
public SHCParser(IWorkerContext context) {
@ -65,16 +62,16 @@ public class SHCParser extends ParserBase {
List<String> list = new ArrayList<>();
String pfx = null;
if (src.startsWith("{")) {
JsonObject json = JsonTrackingParser.parseJson(src);
JsonObject json = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(src);
if (checkProperty(json, "$", "verifiableCredential", true, "Array")) {
pfx = "verifiableCredential";
JsonArray arr = json.getAsJsonArray("verifiableCredential");
JsonArray arr = json.getJsonArray("verifiableCredential");
int i = 0;
for (JsonElement e : arr) {
if (!(e instanceof JsonPrimitive)) {
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), "$.verifiableCredential["+i+"]", IssueType.STRUCTURE, "Wrong Property verifiableCredential in JSON Payload. Expected : String but found "+JsonUtilities.type(e), IssueSeverity.ERROR);
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), "$.verifiableCredential["+i+"]", IssueType.STRUCTURE, "Wrong Property verifiableCredential in JSON Payload. Expected : String but found "+e.type().toName(), IssueSeverity.ERROR);
} else {
list.add(e.getAsString());
list.add(e.asString());
}
i++;
}
@ -95,14 +92,13 @@ public class SHCParser extends ParserBase {
logError(ValidationMessage.NO_RULE_DATE, 1, 1, prefix+"JWT", IssueType.INVALID, "Unable to decode JWT token", IssueSeverity.ERROR);
return res;
}
map = jwt.map;
checkNamedProperties(jwt.getPayload(), prefix+"payload", "iss", "nbf", "vc");
checkProperty(jwt.getPayload(), prefix+"payload", "iss", true, "String");
logError(ValidationMessage.NO_RULE_DATE, 1, 1, prefix+"JWT", IssueType.INFORMATIONAL, "The FHIR Validator does not check the JWT signature "+
"(see https://demo-portals.smarthealth.cards/VerifierPortal.html or https://github.com/smart-on-fhir/health-cards-dev-tools) (Issuer = '"+jwt.getPayload().get("iss").getAsString()+"')", IssueSeverity.INFORMATION);
"(see https://demo-portals.smarthealth.cards/VerifierPortal.html or https://github.com/smart-on-fhir/health-cards-dev-tools) (Issuer = '"+jwt.getPayload().asString("iss")+"')", IssueSeverity.INFORMATION);
checkProperty(jwt.getPayload(), prefix+"payload", "nbf", true, "Number");
JsonObject vc = jwt.getPayload().getAsJsonObject("vc");
JsonObject vc = jwt.getPayload().getJsonObject("vc");
if (vc == null) {
logError(ValidationMessage.NO_RULE_DATE, 1, 1, "JWT", IssueType.STRUCTURE, "Unable to find property 'vc' in the payload", IssueSeverity.ERROR);
return res;
@ -112,13 +108,13 @@ public class SHCParser extends ParserBase {
if (!checkProperty(vc, path, "type", true, "Array")) {
return res;
}
JsonArray type = vc.getAsJsonArray("type");
JsonArray type = vc.getJsonArray("type");
int i = 0;
for (JsonElement e : type) {
if (!(e instanceof JsonPrimitive)) {
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), path+".type["+i+"]", IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : String but found "+JsonUtilities.type(e), IssueSeverity.ERROR);
if (e.type() != JsonElementType.STRING) {
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), path+".type["+i+"]", IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : String but found "+e.type().toName(), IssueSeverity.ERROR);
} else {
types.add(e.getAsString());
types.add(e.asString());
}
i++;
}
@ -129,21 +125,21 @@ public class SHCParser extends ParserBase {
if (!checkProperty(vc, path, "credentialSubject", true, "Object")) {
return res;
}
JsonObject cs = vc.getAsJsonObject("credentialSubject");
JsonObject cs = vc.getJsonObject("credentialSubject");
path = path+".credentialSubject";
if (!checkProperty(cs, path, "fhirVersion", true, "String")) {
return res;
}
JsonElement fv = cs.get("fhirVersion");
if (!VersionUtilities.versionsCompatible(context.getVersion(), fv.getAsString())) {
logError(ValidationMessage.NO_RULE_DATE, line(fv), col(fv), path+".fhirVersion", IssueType.STRUCTURE, "Card claims to be of version "+fv.getAsString()+", cannot be validated against version "+context.getVersion(), IssueSeverity.ERROR);
if (!VersionUtilities.versionsCompatible(context.getVersion(), fv.asString())) {
logError(ValidationMessage.NO_RULE_DATE, line(fv), col(fv), path+".fhirVersion", IssueType.STRUCTURE, "Card claims to be of version "+fv.asString()+", cannot be validated against version "+context.getVersion(), IssueSeverity.ERROR);
return res;
}
if (!checkProperty(cs, path, "fhirBundle", true, "Object")) {
return res;
}
// ok. all checks passed, we can now validate the bundle
Element e = jsonParser.parse(cs.getAsJsonObject("fhirBundle"), map);
Element e = jsonParser.parse(cs.getJsonObject("fhirBundle"));
if (e != null) {
res.add(new NamedElement(path, e));
}
@ -170,7 +166,7 @@ public class SHCParser extends ParserBase {
private boolean checkProperty(JsonObject obj, String path, String name, boolean required, String type) {
JsonElement e = obj.get(name);
if (e != null) {
String t = JsonUtilities.type(e);
String t = e.type().toName();
if (!type.equals(t)) {
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), path+"."+name, IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : "+type+" but found "+t, IssueSeverity.ERROR);
} else {
@ -185,25 +181,19 @@ public class SHCParser extends ParserBase {
}
private void checkNamedProperties(JsonObject obj, String path, String... names) {
for (Entry<String, JsonElement> e : obj.entrySet()) {
if (!Utilities.existsInList(e.getKey(), names)) {
logError(ValidationMessage.NO_RULE_DATE, line(e.getValue()), col(e.getValue()), path+"."+e.getKey(), IssueType.STRUCTURE, "Unknown Property in JSON Payload", IssueSeverity.WARNING);
for (JsonProperty e : obj.getProperties()) {
if (!Utilities.existsInList(e.getName(), names)) {
logError(ValidationMessage.NO_RULE_DATE, line(e.getValue()), col(e.getValue()), path+"."+e.getName(), IssueType.STRUCTURE, "Unknown Property in JSON Payload", IssueSeverity.WARNING);
}
}
}
private int line(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getLine();
return e.getStart().getLine();
}
private int col(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getCol();
return e.getStart().getCol();
}
@ -218,7 +208,6 @@ public class SHCParser extends ParserBase {
private JsonObject header;
private JsonObject payload;
public Map<JsonElement, LocationData> map = new HashMap<>();
public JsonObject getHeader() {
return header;
@ -273,11 +262,11 @@ public class SHCParser extends ParserBase {
throw new FHIRException("The input is not a valid base 64 encoded string.", e);
}
JWT res = new JWT();
res.header = JsonTrackingParser.parseJson(headerJson);
if ("DEF".equals(JsonUtilities.str(res.header, "zip"))) {
res.header = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(headerJson);
if ("DEF".equals(res.header.asString("zip"))) {
payloadJson = inflate(payloadJson);
}
res.payload = JsonTrackingParser.parse(TextFile.bytesToString(payloadJson), res.map, true);
res.payload = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TextFile.bytesToString(payloadJson), true);
return res;
}

View File

@ -30,7 +30,6 @@ package org.hl7.fhir.r5.utils.validation;
*/
import com.google.gson.JsonObject;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.model.Coding;
@ -38,6 +37,7 @@ import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.validation.constants.CheckDisplayOption;
import org.hl7.fhir.r5.utils.validation.constants.IdStatus;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import java.io.IOException;

View File

@ -0,0 +1,37 @@
package org.hl7.fhir.r5.test;
import java.io.IOException;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonObject;
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 testLocations() throws IOException {
String cnt = TestingUtilities.loadTestResource("validator", "slice23", "AuditEvent-ex-FirstSliceProfile.json");
JsonObject json = JsonParser.parseObject(cnt);
checkLine(json, 1, 1);
checkLine(json.get("resourceType"), 2, 19);
checkLine(json.get("id"), 3, 9);
checkLine(json.get("meta"), 4, 11);
checkLine(json.getJsonObject("meta").get("security"), 5, 17);
checkLine(json.get("agent"), 15, 12);
checkLine(json.getJsonArray("agent").get(0), 16, 5);
checkLine(json.getJsonArray("agent").get(0).asJsonObject().get("type"), 17, 15);
checkLine(json.getJsonArray("agent").get(0).asJsonObject().get("requestor"), 32, 20);
checkLine(json.getJsonArray("agent").get(1), 34, 5);
checkLine(json.get("type"), 50, 11);
}
private void checkLine(JsonElement e, int line, int col) {
Assertions.assertEquals(line, e.getStart().getLine(), "line");
Assertions.assertEquals(col, e.getStart().getCol(), "col");
}
}

View File

@ -2,7 +2,6 @@ package org.hl7.fhir.utilities.i18n;
public class I18nConstants {
public static final String ADDING_WRONG_PATH = "Adding_wrong_path";
public static final String ADDING_WRONG_PATH_IN_PROFILE___VS_ = "Adding_wrong_path_in_profile___vs_";
public static final String ADDING_WRONG_PATH__OUTCOMEGETPATH___RESULTPATHBASE__ = "Adding_wrong_path__outcomegetPath___resultPathBase__";
@ -543,6 +542,7 @@ public class I18nConstants {
public static final String UNABLE_TO_FIND_ELEMENT__IN_ = "Unable_to_find_element__in_";
public static final String UNABLE_TO_FIND_PROFILE__AT_ = "Unable_to_find_profile__at_";
public static final String UNABLE_TO_FIND_RESOURCETYPE_PROPERTY = "Unable_to_find_resourceType_property";
public static final String RESOURCETYPE_PROPERTY_WRONG_TYPE = "RESOURCETYPE_PROPERTY_WRONG_TYPE";
public static final String UNABLE_TO_FIND_RESOURCE__AT__RESOLVING_DISCRIMINATOR__FROM_ = "Unable_to_find_resource__at__resolving_discriminator__from_";
public static final String UNABLE_TO_FIND__RESOLVING_DISCRIMINATOR__FROM_ = "Unable_to_find__resolving_discriminator__from_";
public static final String UNABLE_TO_HANDLE_SYSTEM__CONCEPT_FILTER_WITH_OP__ = "Unable_to_handle_system__concept_filter_with_op__";
@ -750,6 +750,13 @@ public class I18nConstants {
public static final String XHTML_XHTML_Entity_Illegal = "XHTML_XHTML_Entity_Illegal";
public static final String UNABLE_TO_RESOLVE_CONTENT_REFERENCE = "UNABLE_TO_RESOLVE_CONTENT_REFERENCE";
public static final String UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT = "UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT";
public static final String DUPLICATE_JSON_PROPERTY = "DUPLICATE_JSON_PROPERTY";
public static final String DUPLICATE_JSON_PROPERTY_KEY = "DUPLICATE_JSON_PROPERTY_KEY";
public static final String JSON_PROPERTY_NO_QUOTES = "JSON_PROPERTY_NO_QUOTES";
public static final String JSON_PROPERTY_VALUE_NO_QUOTES = "JSON_PROPERTY_VALUE_NO_QUOTES";
public static final String JSON_COMMA_MISSING = "JSON_COMMA_MISSING";
public static final String JSON_COMMA_EXTRA = "JSON_COMMA_EXTRA";
public static final String JSON_COMMENTS_NOT_ALLOWED = "JSON_COMMENTS_NOT_ALLOWED";
}

View File

@ -11,6 +11,7 @@ public class JsonArray extends JsonElement implements Iterable<JsonElement> {
private List<JsonElement> items = new ArrayList<>();
private List<Boolean> noCommas; // validator use
private List<Boolean> unQuoted; // validator use
private boolean extraComma; // json5 support
public List<String> asStrings() {
List<String> list = new ArrayList<>();
@ -36,7 +37,7 @@ public class JsonArray extends JsonElement implements Iterable<JsonElement> {
return list;
}
public JsonElementType elementType() {
public JsonElementType type() {
return JsonElementType.ARRAY;
}
@ -125,5 +126,13 @@ public class JsonArray extends JsonElement implements Iterable<JsonElement> {
b.append(" ]");
return b.toString();
}
public boolean isExtraComma() {
return extraComma;
}
public void setExtraComma(boolean extraComma) {
this.extraComma = extraComma;
}
}

View File

@ -11,7 +11,7 @@ public class JsonBoolean extends JsonPrimitive {
private JsonBoolean() {
}
public JsonElementType elementType() {
public JsonElementType type() {
return JsonElementType.BOOLEAN;
}

View File

@ -0,0 +1,28 @@
package org.hl7.fhir.utilities.json.model;
public class JsonComment {
private JsonLocationData start;
private JsonLocationData end;
private String content;
public JsonComment(String content, JsonLocationData start, JsonLocationData end) {
super();
this.content = content;
this.start = start;
this.end = end;
}
public JsonLocationData getStart() {
return start;
}
public JsonLocationData getEnd() {
return end;
}
public String getContent() {
return content;
}
}

View File

@ -7,13 +7,13 @@ import org.hl7.fhir.utilities.json.JsonException;
public abstract class JsonElement {
private List<String> comments;
private List<JsonComment> comments;
private JsonLocationData start;
private JsonLocationData end;
public abstract JsonElementType elementType();
public abstract JsonElementType type();
public List<String> getComments() {
public List<JsonComment> getComments() {
if (comments == null ) {
comments = new ArrayList<>();
}
@ -54,11 +54,11 @@ public abstract class JsonElement {
protected abstract JsonElement make();
public boolean isJsonObject() {
return elementType() == JsonElementType.OBJECT;
return type() == JsonElementType.OBJECT;
}
public boolean isJsonArray() {
return elementType() == JsonElementType.ARRAY;
return type() == JsonElementType.ARRAY;
}
public boolean isJsonPrimitive() {
@ -66,19 +66,19 @@ public abstract class JsonElement {
}
public boolean isJsonBoolean() {
return elementType() == JsonElementType.BOOLEAN;
return type() == JsonElementType.BOOLEAN;
}
public boolean isJsonString() {
return elementType() == JsonElementType.STRING;
return type() == JsonElementType.STRING;
}
public boolean isJsonNumber() {
return elementType() == JsonElementType.NUMBER;
return type() == JsonElementType.NUMBER;
}
public boolean isJsonNull() {
return elementType() == JsonElementType.NULL;
return type() == JsonElementType.NULL;
}
public JsonObject asJsonObject() {

View File

@ -2,4 +2,16 @@ package org.hl7.fhir.utilities.json.model;
public enum JsonElementType {
OBJECT, ARRAY, STRING, NUMBER, BOOLEAN, NULL;
public String toName() {
switch (this) {
case ARRAY: return "Array";
case BOOLEAN: return "Boolean";
case NULL: return "Null";
case NUMBER: return "Number";
case OBJECT: return "Object";
case STRING: return "String";
default: return "??";
}
}
}

View File

@ -50,7 +50,7 @@ public class JsonLocationData {
public void back() {
if (col == 1) {
line--;
line = lastCol;
col = lastCol;
} else {
col--;
}

View File

@ -2,7 +2,7 @@ package org.hl7.fhir.utilities.json.model;
public class JsonNull extends JsonPrimitive {
public JsonElementType elementType() {
public JsonElementType type() {
return JsonElementType.NULL;
}

View File

@ -17,7 +17,7 @@ public class JsonNumber extends JsonPrimitive {
private JsonNumber() {
}
public JsonElementType elementType() {
public JsonElementType type() {
return JsonElementType.NUMBER;
}

View File

@ -16,8 +16,9 @@ public class JsonObject extends JsonElement {
private List<JsonProperty> properties = new ArrayList<>();
private Map<String, JsonProperty> propMap = new HashMap<>();
private boolean extraComma; // json5 support
public JsonElementType elementType() {
public JsonElementType type() {
return JsonElementType.OBJECT;
}
@ -151,11 +152,11 @@ public class JsonObject extends JsonElement {
}
public boolean hasObject(String name) {
return propMap.containsKey(name) && propMap.get(name).getValue().elementType() == JsonElementType.OBJECT;
return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.OBJECT;
}
public boolean hasArray(String name) {
return propMap.containsKey(name) && propMap.get(name).getValue().elementType() == JsonElementType.ARRAY;
return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.ARRAY;
}
public boolean hasPrimitive(String name) {
@ -163,19 +164,19 @@ public class JsonObject extends JsonElement {
}
public boolean hasString(String name) {
return propMap.containsKey(name) && propMap.get(name).getValue().elementType() == JsonElementType.STRING;
return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.STRING;
}
public boolean hasNumber(String name) {
return propMap.containsKey(name) && propMap.get(name).getValue().elementType() == JsonElementType.NUMBER;
return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.NUMBER;
}
public boolean hasBoolean(String name) {
return propMap.containsKey(name) && propMap.get(name).getValue().elementType() == JsonElementType.BOOLEAN;
return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.BOOLEAN;
}
public boolean hasNull(String name) {
return propMap.containsKey(name) && propMap.get(name).getValue().elementType() == JsonElementType.NULL;
return propMap.containsKey(name) && propMap.get(name).getValue().type() == JsonElementType.NULL;
}
@ -349,5 +350,14 @@ public class JsonObject extends JsonElement {
b.append(" }");
return b.toString();
}
public boolean isExtraComma() {
return extraComma;
}
public void setExtraComma(boolean extraComma) {
this.extraComma = extraComma;
}
}

View File

@ -13,7 +13,7 @@ public class JsonString extends JsonPrimitive {
private JsonString() {
}
public JsonElementType elementType() {
public JsonElementType type() {
return JsonElementType.STRING;
}

View File

@ -6,6 +6,7 @@ import java.util.List;
import java.util.Stack;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonComment;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonLocationData;
@ -46,7 +47,7 @@ public class JsonLexer {
private StringBuilder b = new StringBuilder();
private boolean allowComments;
private boolean allowUnquotedStrings;
private List<String> comments = new ArrayList<>();
private List<JsonComment> comments = new ArrayList<>();
private boolean isUnquoted;
public JsonLexer(String source, boolean allowComments, boolean allowUnquotedStrings) throws IOException {
@ -166,6 +167,7 @@ public class JsonLexer {
do {
ch = getNextChar();
if (allowComments && ch == '/') {
JsonLocationData start = location.prev();
char ch1 = getNextChar();
if (ch1 == '/') {
StringBuilder b = new StringBuilder();
@ -174,7 +176,7 @@ public class JsonLexer {
if (first) first = false; else b.append(ch);
ch = getNextChar();
}
comments.add(b.toString().trim());
comments.add(new JsonComment(b.toString().trim(), start, location.prev()));
} else {
push(ch1);
}
@ -293,5 +295,10 @@ public class JsonLexer {
return isUnquoted;
}
@Override
public String toString() {
return "JsonLexer [cursor=" + cursor + ", peek=" + peek + ", type=" + type + ", location=" + location.toString() + "]";
}
}

View File

@ -14,6 +14,7 @@ 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.JsonComment;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonElementType;
import org.hl7.fhir.utilities.json.model.JsonLocationData;
@ -305,6 +306,7 @@ public class JsonParser {
private void readObject(String path, JsonObject obj, boolean root) throws IOException, JsonException {
while (!(itemType == ItemType.End) || (root && (itemType == ItemType.Eof))) {
obj.setExtraComma(false);
switch (itemType) {
case Object:
JsonObject child = new JsonObject(); //(obj.path+'.'+ItemName);
@ -374,6 +376,7 @@ public class JsonParser {
}
itemNoComma = false;
endProperty = lexer.getLocation().copy();
obj.setExtraComma(lexer.getType() == TokenType.Comma);
next();
}
}
@ -382,6 +385,7 @@ public class JsonParser {
boolean res = false;
while (!((itemType == ItemType.End) || (root && (itemType == ItemType.Eof)))) {
res = true;
arr.setExtraComma(false);
switch (itemType) {
case Object:
JsonObject obj = new JsonObject(); // (arr.path+'['+inttostr(i)+']');
@ -436,6 +440,7 @@ public class JsonParser {
}
itemNoComma = false;
arr.setEnd(lexer.getLocation().copy());
arr.setExtraComma(lexer.getType() == TokenType.Comma);
next();
}
return res;
@ -465,7 +470,12 @@ public class JsonParser {
lexer.getStates().pop();
if (lexer.getType() == TokenType.Comma) {
lexer.next();
parseProperty();
if (allowNoComma && (lexer.getType() == TokenType.CloseArray || lexer.getType() == TokenType.Close)) {
itemType = ItemType.End;
lexer.next();
} else {
parseProperty();
}
} else if (lexer.getType() == TokenType.Close) {
itemType = ItemType.End;
lexer.next();
@ -551,17 +561,17 @@ public class JsonParser {
return b.toString();
}
private void writeComments(StringBuilder b, List<String> comments, int indent) {
for (String s : comments) {
private void writeComments(StringBuilder b, List<JsonComment> comments, int indent) {
for (JsonComment s : comments) {
b.append("// ");
b.append(s);
b.append(s.getContent());
b.append("\n");
b.append(Utilities.padLeft("", ' ', indent));
}
}
private void write(StringBuilder b, JsonElement e, boolean pretty, int indent) {
switch (e.elementType()) {
switch (e.type()) {
case ARRAY:
JsonArray arr = (JsonArray) e;
b.append("[");
@ -573,7 +583,7 @@ public class JsonParser {
if (i instanceof JsonPrimitive) {
length = length + ((JsonPrimitive)i).toJson().length();
}
if (i.elementType() == JsonElementType.ARRAY || i.elementType() == JsonElementType.OBJECT
if (i.type() == JsonElementType.ARRAY || i.type() == JsonElementType.OBJECT
|| i.hasComments()) { // 20 is a somewhat arbitrary cut off
complex = true;
}

View File

@ -393,7 +393,7 @@ Error_parsing_JSON_the_primitive_value_must_be_a_number = Error parsing JSON: th
Error_parsing_JSON_the_primitive_value_must_be_a_boolean = Error parsing JSON: the primitive value must be a boolean
Error_parsing_XHTML_ = Error parsing XHTML: {0}
This_property_must_be_an_object_not_ = This property must be an object, not {0} ({1} at {2})
This_property_must_be_an_simple_value_not_ = This property must be an simple value, not {0} ({1} at {2})
This_property_must_be_an_simple_value_not_ = This property must be a simple value, not {0} ({1} at {2})
This_property_must_be__not_ = The property {2} must be {0}, not {1} (at {3})
This_property_must_be_an_Array_not_ = The property {1} must be a JSON Array, not {0} (at {2})
OBJECT_CANNOT_BE_KEYED_ARRAY_CHILD_COUNT = This object cannot be an keyed Array in Json because it does not have two children in the definitions (children = {0})
@ -480,7 +480,7 @@ XHTML_URL_INVALID = The URL is not valid because ''({1})'': {0}
MEASURE_MR_GRP_NO_CODE = Group should have a code that matches the group definition in the measure
MEASURE_MR_GRP_UNK_CODE = The code for this group has no match in the measure definition
MEASURE_MR_GRP_DUPL_CODE = The code for this group is duplicated with another group
MEASURE_MR_GRP_MISSING_BY_CODE = The MeasureReport does not include a group for the group {0}
MEASURE_MR_GRP_MISSING_BY_CODE = The MeasureReport does not include a group for the group ''{0}''
MEASURE_MR_GRP_NO_USABLE_CODE = None of the codes provided are usable for comparison - need both system and code on at least one code
MEASURE_MR_GRP_NO_WRONG_CODE = The code provided ({0}) does not match the code specified in the measure report ({1})
DUPLICATE_ID = Duplicate id value ''{0}''
@ -799,3 +799,11 @@ SD_CONSTRAINED_KIND_NO_MATCH = The kind {0} must be the same as the kind {1} in
SD_PATH_TYPE_MISMATCH = The path {1} must start with the type of the structure {0}
UNABLE_TO_RESOLVE_CONTENT_REFERENCE = Unable to resolve the content reference {0} on element {1} (path = {2})
UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT = Unable to resolve the content reference {0} on element {1} (path = {2}) in this context
RESOURCETYPE_PROPERTY_WRONG_TYPE = The JSON element ''resourceType'' has the wrong JSON type: {0}
DUPLICATE_JSON_PROPERTY = The JSON property ''{0}'' is a duplicate and will be ignored
DUPLICATE_JSON_PROPERTY_KEY = The JSON property ''{0}'' is a duplicate and will be ignored
JSON_PROPERTY_NO_QUOTES = The JSON property ''{0}'' has no quotes around the name of the property
JSON_PROPERTY_VALUE_NO_QUOTES = The JSON property ''{0}'' has no quotes around the value of the property ''{1}''
JSON_COMMA_MISSING = A Comma is missing in the JSON
JSON_COMMA_EXTRA = There is an extra comma at the end of the {0} in the JSON
JSON_COMMENTS_NOT_ALLOWED = Comments are not allowed in JSON

View File

@ -28,7 +28,12 @@ public class JsonParserTests {
Assertions.assertEquals(0, obj.getComments().size());
JsonString c = obj.getJsonString("n1");
Assertions.assertEquals(1, c.getComments().size());
Assertions.assertEquals("some comment", c.getComments().get(0));
Assertions.assertEquals("some comment", c.getComments().get(0).getContent());
Assertions.assertEquals(2, c.getComments().get(0).getStart().getLine());
Assertions.assertEquals(3, c.getComments().get(0).getStart().getCol());
Assertions.assertEquals(2, c.getComments().get(0).getEnd().getLine());
Assertions.assertEquals(19, c.getComments().get(0).getEnd().getCol());
Assertions.assertEquals("some comment", c.getComments().get(0).getContent());
Assertions.assertEquals("{\"n1\":\"v1\"}", JsonParser.compose(obj, false));
Assertions.assertEquals("{\n // some comment\n \"n1\" : \"v1\"\n}\n", JsonParser.compose(obj, true));
}
@ -37,7 +42,11 @@ public class JsonParserTests {
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));
Assertions.assertEquals("some comment", obj.getComments().get(0).getContent());
Assertions.assertEquals(1, obj.getComments().get(0).getStart().getLine());
Assertions.assertEquals(1, obj.getComments().get(0).getStart().getCol());
Assertions.assertEquals(1, obj.getComments().get(0).getEnd().getLine());
Assertions.assertEquals(16, obj.getComments().get(0).getEnd().getCol());
JsonString c = obj.getJsonString("n1");
Assertions.assertEquals(0, c.getComments().size());
Assertions.assertEquals("{\"n1\":\"v1\"}", JsonParser.compose(obj, false));
@ -477,10 +486,10 @@ public class JsonParserTests {
@Test
public void testElementBool() throws IOException, JsonException {
JsonElement e = JsonParser.parse("true");
Assertions.assertEquals(JsonElementType.BOOLEAN, e.elementType());
Assertions.assertEquals(JsonElementType.BOOLEAN, e.type());
Assertions.assertEquals(true, ((JsonBoolean) e).isValue());
e = JsonParser.parse("// comment\nfalse", true);
Assertions.assertEquals(JsonElementType.BOOLEAN, e.elementType());
Assertions.assertEquals(JsonElementType.BOOLEAN, e.type());
Assertions.assertEquals(false, ((JsonBoolean) e).isValue());
Assertions.assertEquals("false", JsonParser.compose(e));
Assertions.assertEquals("// comment\nfalse\n", JsonParser.compose(e, true));
@ -489,10 +498,10 @@ public class JsonParserTests {
@Test
public void testElementNumber() throws IOException, JsonException {
JsonElement e = JsonParser.parse("1");
Assertions.assertEquals(JsonElementType.NUMBER, e.elementType());
Assertions.assertEquals(JsonElementType.NUMBER, e.type());
Assertions.assertEquals("1", ((JsonNumber) e).getValue());
e = JsonParser.parse("// comment \n-1.2e10", true);
Assertions.assertEquals(JsonElementType.NUMBER, e.elementType());
Assertions.assertEquals(JsonElementType.NUMBER, e.type());
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));
@ -501,19 +510,19 @@ public class JsonParserTests {
@Test
public void testElementString() throws IOException, JsonException {
JsonElement e = JsonParser.parse("\"str\\ning\"");
Assertions.assertEquals(JsonElementType.STRING, e.elementType());
Assertions.assertEquals(JsonElementType.STRING, e.type());
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(JsonElementType.STRING, e.type());
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(JsonElementType.NULL, e.type());
Assertions.assertEquals("null", JsonParser.compose(e));
Assertions.assertEquals("null\n", JsonParser.compose(e, true));
}
@ -521,12 +530,12 @@ public class JsonParserTests {
@Test
public void testElementArray() throws IOException, JsonException {
JsonElement e = JsonParser.parse("[\"test\", null, true]");
Assertions.assertEquals(JsonElementType.ARRAY, e.elementType());
Assertions.assertEquals(JsonElementType.ARRAY, e.type());
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(JsonElementType.ARRAY, e.type());
Assertions.assertEquals(0, ((JsonArray) e).size());
}
@ -567,8 +576,20 @@ public class JsonParserTests {
Assertions.assertEquals(2, e.size());
Assertions.assertEquals(false, e.isNoComma(0));
Assertions.assertEquals(true, e.isNoComma(1));
Assertions.assertEquals(false, e.isExtraComma());
}
@Test
public void testNoCommainArr3() throws IOException, JsonException {
JsonArray e = (JsonArray) JsonParser.parse("[1, 2 3,]", true);
Assertions.assertEquals(3, e.size());
Assertions.assertEquals(false, e.isNoComma(0));
Assertions.assertEquals(false, e.isNoComma(1));
Assertions.assertEquals(true, e.isNoComma(2));
Assertions.assertEquals(true, e.isExtraComma());
}
@Test
public void testUnquoted1() throws IOException, JsonException {
@ -583,5 +604,14 @@ public class JsonParserTests {
Assertions.assertEquals(true, e.getProperties().get(0).isUnquotedValue());
Assertions.assertEquals(false, e.getProperties().get(1).isUnquotedName());
Assertions.assertEquals(true, e.getProperties().get(1).isUnquotedValue());
Assertions.assertEquals(false, e.isExtraComma());
}
@Test
public void testExtraComma() throws IOException, JsonException {
JsonObject o = (JsonObject) JsonParser.parse("{ \"a\" : \"b\", \"c\" : \"d\",}", true);
Assertions.assertEquals(2, o.getProperties().size());
Assertions.assertEquals(true, o.isExtraComma());
}
}

View File

@ -172,6 +172,7 @@ import org.hl7.fhir.utilities.Utilities.DecimalStatus;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -199,9 +200,6 @@ import org.hl7.fhir.validation.instance.utils.ResourceValidationTracker;
import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
import org.w3c.dom.Document;
import com.google.gson.JsonObject;
/**
* Thinking of using this in a java program? Don't!
* You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine
@ -429,6 +427,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean wantCheckSnapshotUnchanged;
private boolean noUnicodeBiDiControlChars;
private HtmlInMarkdownCheck htmlInMarkdownCheck;
private boolean allowComments;
private List<ImplementationGuide> igs = new ArrayList<>();
private List<String> extensionDomains = new ArrayList<String>();
@ -580,6 +579,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
this.allowExamples = value;
}
public boolean isAllowComments() {
return allowComments;
}
public void setAllowComments(boolean allowComments) {
this.allowComments = allowComments;
}
public boolean isCrumbTrails() {
return crumbTrails;
}
@ -677,6 +684,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
}
parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
if (parser instanceof XmlParser) {
((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
}
if (parser instanceof JsonParser) {
((JsonParser) parser).setAllowComments(allowComments);
}
long t = System.nanoTime();
List<NamedElement> list = null;
try {

View File

@ -265,6 +265,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
if (content.has("best-practice")) {
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.valueOf(content.get("best-practice").getAsString()));
}
if (content.has("allow-comments")) {
val.setAllowComments(content.get("allow-comments").getAsBoolean());
}
if (content.has("examples")) {
val.setAllowExamples(content.get("examples").getAsBoolean());
} else {
@ -368,9 +371,6 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
checkOutcomes(errorsLogical, logical, "logical", name);
}
logger.verifyHasNoRequests();
if (BUILD_NEW) {
JsonTrackingParser.write(manifest, new File(Utilities.path("[tmp]", "validator", "manifest.new.json")));
}
}
@ -444,6 +444,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
OperationOutcome actual = OperationOutcomeUtilities.createOutcomeSimple(errors);
actual.setText(null);
String json = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(actual);
List<String> fails = new ArrayList<>();
@ -482,7 +483,32 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
logOutput(json);
logOutput("");
logOutput("========================================================");
logOutput("");
logOutput("");
if (BUILD_NEW && actual.getIssue().size() > 0) {
if (java.has("output")) {
java.remove("output");
}
if (java.has("error-locations")) {
java.remove("error-locations");
}
if (java.has("warningCount")) {
java.remove("warningCount");
}
if (java.has("infoCount")) {
java.remove("infoCount");
}
if (java.has("errorCount")) {
java.remove("errorCount");
}
if (java.has("outcome")) {
java.remove("outcome");
}
if (actual.hasIssue()) {
JsonObject oj = JsonTrackingParser.parse(json, null);
java.add("outcome", oj);
}
JsonTrackingParser.write(manifest, new File(Utilities.path("[tmp]", "validator", "manifest.new.json")));
}
Assertions.fail("\r\n"+String.join("\r\n", fails));
}
// int ec = 0;
@ -526,30 +552,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
// Assert.assertEquals("Location should be " + el.get(i).getAsString() + ", but was " + errLocs.get(i), errLocs.get(i), el.get(i).getAsString());
// }
// }
if (BUILD_NEW) {
if (java.has("output")) {
java.remove("output");
}
if (java.has("error-locations")) {
java.remove("error-locations");
}
if (java.has("warningCount")) {
java.remove("warningCount");
}
if (java.has("infoCount")) {
java.remove("infoCount");
}
if (java.has("errorCount")) {
java.remove("errorCount");
}
if (java.has("outcome")) {
java.remove("outcome");
}
if (actual.hasIssue()) {
JsonObject oj = JsonTrackingParser.parse(json, null);
java.add("outcome", oj);
}
}
}
private void logOutput(String msg) {