![](/hapi-fhir/images/logos/raccoon-forwards.png)
001package org.hl7.fhir.r5.elementmodel; 002 003import java.io.ByteArrayInputStream; 004 005/* 006 Copyright (c) 2011+, HL7, Inc. 007 All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modification, 010 are permitted provided that the following conditions are met: 011 012 * Redistributions of source code must retain the above copyright notice, this 013 list of conditions and the following disclaimer. 014 * Redistributions in binary form must reproduce the above copyright notice, 015 this list of conditions and the following disclaimer in the documentation 016 and/or other materials provided with the distribution. 017 * Neither the name of HL7 nor the names of its contributors may be used to 018 endorse or promote products derived from this software without specific 019 prior written permission. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 024 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 025 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 026 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 028 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 029 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 030 POSSIBILITY OF SUCH DAMAGE. 031 032 */ 033 034 035 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.OutputStream; 039import java.io.OutputStreamWriter; 040import java.math.BigDecimal; 041import java.nio.charset.StandardCharsets; 042import java.util.ArrayList; 043import java.util.HashMap; 044import java.util.HashSet; 045import java.util.List; 046import java.util.Map; 047import java.util.Map.Entry; 048import java.util.Set; 049 050import org.hl7.fhir.exceptions.FHIRException; 051import org.hl7.fhir.exceptions.FHIRFormatError; 052import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 053import org.hl7.fhir.r5.context.ContextUtilities; 054import org.hl7.fhir.r5.context.IWorkerContext; 055import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; 056import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; 057import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 058import org.hl7.fhir.r5.formats.IParser.OutputStyle; 059import org.hl7.fhir.r5.formats.JsonCreator; 060import org.hl7.fhir.r5.formats.JsonCreatorCanonical; 061import org.hl7.fhir.r5.formats.JsonCreatorDirect; 062import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 063import org.hl7.fhir.r5.model.ElementDefinition; 064import org.hl7.fhir.r5.model.StructureDefinition; 065import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 066import org.hl7.fhir.utilities.StringPair; 067import org.hl7.fhir.utilities.TextFile; 068import org.hl7.fhir.utilities.Utilities; 069import org.hl7.fhir.utilities.VersionUtilities; 070import org.hl7.fhir.utilities.i18n.I18nConstants; 071import org.hl7.fhir.utilities.json.model.JsonArray; 072import org.hl7.fhir.utilities.json.model.JsonComment; 073import org.hl7.fhir.utilities.json.model.JsonElement; 074import org.hl7.fhir.utilities.json.model.JsonNull; 075import org.hl7.fhir.utilities.json.model.JsonObject; 076import org.hl7.fhir.utilities.json.model.JsonPrimitive; 077import org.hl7.fhir.utilities.json.model.JsonProperty; 078import org.hl7.fhir.utilities.validation.ValidationMessage; 079import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 080import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 081import org.hl7.fhir.utilities.xhtml.XhtmlParser; 082 083 084public class JsonParser extends ParserBase { 085 086 private JsonCreator json; 087 private boolean allowComments; 088 089 private Element baseElement; 090 private boolean markedXhtml; 091 092 public JsonParser(IWorkerContext context, ProfileUtilities utilities) { 093 super(context, utilities); 094 095 } 096 097 public JsonParser(IWorkerContext context) { 098 super(context); 099 } 100 101 public Element parse(String source, String type) throws Exception { 102 return parse(source, type, false); 103 } 104 105 public Element parse(String source, String type, boolean inner) throws Exception { 106 ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", source.getBytes(StandardCharsets.UTF_8), false); 107 JsonObject obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true); 108 String path = "/"+type; 109 StructureDefinition sd = getDefinition(focusFragment.getErrors(), -1, -1, type); 110 if (sd == null) { 111 return null; 112 } 113 114 if (inner) { 115 // we have an anonymous wrapper that has an arbitrarily named property with the specified type. We're going to invent a snapshot for that 116 sd = new StructureDefinition(); 117 sd.setType("Wrapper"); 118 ElementDefinition bEd = sd.getSnapshot().addElement(); 119 ElementDefinition nEd = sd.getSnapshot().addElement(); 120 bEd.setPath("Wrapper"); 121 nEd.setPath("Wrapper."+obj.getProperties().get(0).getName()); 122 nEd.addType().setCode(type); 123 nEd.setMax(obj.getProperties().get(0).getValue().isJsonArray() ? "*" : "1"); 124 } 125 Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON); 126 result.setPath(type); 127 checkObject(focusFragment.getErrors(), obj, result, path); 128 result.setType(type); 129 parseChildren(focusFragment.getErrors(), path, obj, result, true, null); 130 result.numberChildren(); 131 return result; 132 } 133 134 135 @Override 136 public List<ValidatedFragment> parse(InputStream inStream) throws IOException, FHIRException { 137 return parse(inStream, 0); 138 } 139 140 public List<ValidatedFragment> parse(InputStream inStream, int line) throws IOException, FHIRException { 141// long start = System.currentTimeMillis(); 142 byte[] content = TextFile.streamToBytes(inStream); 143 ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", content, false); 144 145 ByteArrayInputStream stream = new ByteArrayInputStream(content); 146 147 // if we're parsing at this point, then we're going to use the custom parser 148 String source = TextFile.streamToString(stream); 149 JsonObject obj = null; 150 151 if (policy == ValidationPolicy.EVERYTHING) { 152 try { 153 obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line); 154 } catch (Exception e) { 155 logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, -1, -1, null, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL); 156 } 157 } else { 158 obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line); 159 } 160 161 if (obj != null) { 162 focusFragment.setElement(parse(focusFragment.getErrors(), obj)); 163 } 164 List<ValidatedFragment> res = new ArrayList<>(); 165 res.add(focusFragment); 166 167// long t =System.currentTimeMillis()-start; 168// System.out.println("json parser: "+(t)+"ms, "+(content.length/1024)+"kb "+(t == 0 ? "" : " @ "+(content.length / t)+"kb/s")); 169 return res; 170 } 171 172 public Element parse(List<ValidationMessage> errors, JsonObject object) throws FHIRException { 173 return parse(errors, object, null); 174 } 175 176 public Element parse(List<ValidationMessage> errors, JsonObject object, String statedPath) throws FHIRException { 177 if (object == null) { 178 System.out.println("What?"); 179 } 180 StructureDefinition sd = getLogical(); 181 String name; 182 String path; 183 if (sd == null) { 184 JsonElement rt = object.get("resourceType"); 185 if (rt == null) { 186 logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL); 187 return null; 188 } else if (!rt.isJsonString()) { 189 logError(errors, "2022-11-26", line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL); 190 return null; 191 } else { 192 name = rt.asString(); 193 194 sd = getDefinition(errors, line(object), col(object), name); 195 if (sd == null) { 196 return null; 197 } 198 } 199 path = statedPath == null ? name : statedPath; 200 } else { 201 name = sd.getType(); 202 path = statedPath == null ? sd.getTypeTail() : statedPath; 203 } 204 baseElement = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON); 205 checkObject(errors, object, baseElement, path); 206 baseElement.markLocation(line(object), col(object)); 207 baseElement.setType(name); 208 baseElement.setPath(statedPath == null ? baseElement.fhirTypeRoot() : statedPath); 209 parseChildren(errors, path, object, baseElement, true, null); 210 baseElement.numberChildren(); 211 return baseElement; 212 } 213 214 private void checkObject(List<ValidationMessage> errors, JsonObject object, Element b, String path) { 215 b.setNativeObject(object); 216 checkComments(errors, object, b, path); 217 if (policy == ValidationPolicy.EVERYTHING) { 218 if (object.getProperties().size() == 0) { 219 logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR); 220 } 221 } 222 } 223 224 private void checkComments(List<ValidationMessage> errors, JsonElement element, Element b, String path) throws FHIRFormatError { 225 if (element != null && element.hasComments()) { 226 if (allowComments) { 227 for (JsonComment c : element.getComments()) { 228 b.getComments().add(c.getContent()); 229 } 230 } else { 231 for (JsonComment c : element.getComments()) { 232 logError(errors, "2022-11-26", c.getStart().getLine(), c.getStart().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMENTS_NOT_ALLOWED), IssueSeverity.ERROR); 233 } 234 } 235 } 236 } 237 238 private List<Property> parseChildren(List<ValidationMessage> errors, String path, JsonObject object, Element element, boolean hasResourceType, List<Property> properties) throws FHIRException { 239 if (properties == null) { 240 // save time refetching these if we're in a loop 241 properties = element.getProperty().getChildProperties(element.getName(), null); 242 } 243 processChildren(errors, path, object); 244 245 // first pass: process the properties 246 for (Property property : properties) { 247 parseChildItem(errors, path, object.getProperties(), element, property); 248 } 249 250 // second pass: check for things not processed (including duplicates) 251 checkNotProcessed(errors, path, element, hasResourceType, object.getProperties()); 252 253 254 if (object.isExtraComma()) { 255 logError(errors, "2022-11-26", object.getEnd().getLine(), object.getEnd().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR); 256 } 257 return properties; 258 } 259 260 private void checkNotProcessed(List<ValidationMessage> errors, String path, Element element, boolean hasResourceType, List<JsonProperty> children) { 261 if (policy != ValidationPolicy.NONE) { 262 for (JsonProperty e : children) { 263 if (e.getTag() == 0) { 264 StructureDefinition sd = element.getProperty().isLogical() ? getContextUtilities().fetchByJsonName(e.getName()) : null; 265 if (sd != null) { 266 Property property = new Property(context, sd.getSnapshot().getElementFirstRep(), sd, element.getProperty().getUtils(), element.getProperty().getContextUtils()); 267 parseChildItem(errors, path, children, element, property); 268 } else if ("fhir_comments".equals(e.getName()) && (VersionUtilities.isR2BVer(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion()))) { 269 if (!e.getValue().isJsonArray()) { 270 logError(errors, "2022-12-17", line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.ILLEGAL_COMMENT_TYPE, e.getValue().type().toName()), IssueSeverity.ERROR); 271 } else { 272 for (JsonElement c : e.getValue().asJsonArray()) { 273 if (!c.isJsonString()) { 274 logError(errors, "2022-12-17", line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.ILLEGAL_COMMENT_TYPE, c.type().toName()), IssueSeverity.ERROR); 275 } else { 276 element.getComments().add(c.asString()); 277 } 278 } 279 } 280 } else if (hasResourceType && "resourceType".equals(e.getName())) { 281 // nothing 282 } else { 283 JsonProperty p = getFoundJsonPropertyByName(e.getName(), children); 284 if (p != null) { 285 logError(errors, "2022-11-26", line(e.getValue()), col(e.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, e.getName()), IssueSeverity.ERROR); 286 } else { 287 logError(errors, ValidationMessage.NO_RULE_DATE, line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getName()), IssueSeverity.ERROR); 288 } 289 } 290 } 291 } 292 } 293 } 294 295 private JsonProperty getFoundJsonPropertyByName(String name, List<JsonProperty> children) { 296 int hash = name.hashCode(); 297 for (JsonProperty p : children) { 298 if (p.getTag() == 1 && hash == p.getNameHash()) { 299 return p; 300 } 301 } 302 return null; 303 } 304 305 private JsonProperty getJsonPropertyByName(String name, List<JsonProperty> children) { 306 int hash = name.hashCode(); 307 for (JsonProperty p : children) { 308 if (p.getTag() == 0 && hash == p.getNameHash()) { 309 return p; 310 } 311 } 312 return null; 313 } 314 315 private JsonProperty getJsonPropertyByBaseName(String name, List<JsonProperty> children) { 316 for (JsonProperty p : children) { 317 if (p.getTag() == 0 && p.getName().startsWith(name)) { 318 return p; 319 } 320 } 321 return null; 322 } 323 324 private void processChildren(List<ValidationMessage> errors, String path, JsonObject object) { 325 for (JsonProperty p : object.getProperties()) { 326 if (p.isUnquotedName()) { 327 logError(errors, "2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_PROPERTY_NO_QUOTES, p.getName()), IssueSeverity.ERROR); 328 } 329 if (p.isNoComma()) { 330 logError(errors, "2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_MISSING), IssueSeverity.ERROR); 331 } 332 } 333 } 334 335 public void parseChildItem(List<ValidationMessage> errors, String path, List<JsonProperty> children, Element context, Property property) { 336 JsonProperty jp = getJsonPropertyByName(property.getJsonName(), children); 337 if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) { 338 if (property.isJsonPrimitiveChoice()) { 339 if (jp != null) { 340 jp.setTag(1); 341 JsonElement je = jp.getValue(); 342 String type = getTypeFromJsonType(je); 343 if (type == null) { 344 logError(errors, 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); 345 } else if (property.hasType(type)) { 346 Property np = new Property(property.getContext(), property.getDefinition(), property.getStructure(), property.getUtils(), property.getContextUtils(), type); 347 parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, np, path, property.getName(), false); 348 } else { 349 logError(errors, 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); 350 } 351 } 352 } else { 353 String baseName = property.getJsonName().substring(0, property.getName().length()-3); 354 jp = getJsonPropertyByBaseName(baseName, children); 355 JsonProperty jp1 = getJsonPropertyByBaseName("_"+baseName, children); 356 if (jp != null || jp1 != null) { 357 for (TypeRefComponent type : property.getDefinition().getType()) { 358 String eName = baseName + Utilities.capitalize(type.getWorkingCode()); 359 if ((jp != null && jp.getName().equals(eName) || (jp1 != null && jp1.getName().equals("_"+eName)))) { 360 if (!isPrimitive(type.getWorkingCode()) && jp != null) { 361 parseChildComplex(errors, path, jp, context, property, eName, false); 362 break; 363 } else if (isPrimitive(type.getWorkingCode()) && (jp != null || jp1 != null)) { 364 parseChildPrimitive(errors, jp, jp1, context, property, path, eName, false); 365 break; 366 } 367 } 368 } 369 } 370 } 371 } else if (property.isPrimitive(property.getType(null))) { 372 parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, property, path, property.getJsonName(), property.hasJsonName()); 373 } else if (jp != null) { 374 parseChildComplex(errors, path, jp, context, property, property.getJsonName(), property.hasJsonName()); 375 } 376 } 377 378 379 private String getTypeFromJsonType(JsonElement je) { 380 if (je.isJsonPrimitive()) { 381 JsonPrimitive p = je.asJsonPrimitive(); 382 if (p.isJsonString()) { 383 return "string"; 384 } else if (p.isJsonBoolean()) { 385 return "boolean"; 386 } else { 387 String s = p.asString(); 388 if (Utilities.isInteger(s)) { 389 return "integer"; 390 } else { 391 return "decimal"; 392 } 393 } 394 } else { 395 return null; 396 } 397 } 398 399 private void parseChildComplex(List<ValidationMessage> errors, String path, JsonProperty p, Element element, Property property, String name, boolean isJsonName) throws FHIRException { 400 String npath = path+"."+property.getName(); 401 String fpath = element.getPath()+"."+property.getName(); 402 if (p != null) { p.setTag(1); } 403 JsonElement e = p == null ? null : p.getValue(); 404 if (property.isList() && !property.isJsonKeyArray() && (e instanceof JsonArray)) { 405 JsonArray arr = (JsonArray) e; 406 if (arr.isExtraComma()) { 407 logError(errors, "2022-11-26", arr.getEnd().getLine(), arr.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR); 408 } 409 if (arr.size() == 0) { 410 if (property.canBeEmpty()) { 411 // nothing 412 } else { 413 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR); 414 } 415 } 416 int c = 0; 417 List<Property> properties = null; 418 for (JsonElement am : arr) { 419 properties = parseChildComplexInstance(errors, npath+"["+c+"]", fpath+"["+c+"]", element, property, name, am, c == 0 ? arr : null, path, properties); 420 c++; 421 } 422 } else if (property.isJsonKeyArray()) { 423 String code = property.getJsonKeyProperty(); 424 List<Property> properties = property.getChildProperties(element.getName(), null); 425 if (properties.size() != 2) { 426 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_CHILD_COUNT, propNames(properties)), IssueSeverity.ERROR); 427 } else { 428 Property propK = properties.get(0); 429 Property propV = properties.get(1); 430 if (!propK.getName().equals(code)) { 431 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_PROP_NAME, propNames(properties)), IssueSeverity.ERROR); 432 } else if (!propK.isPrimitive()) { 433 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_PROP_TYPE, propNames(properties), propK.typeSummary()), IssueSeverity.ERROR); 434 } else if (propV.isList()) { 435 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_NO_LIST, propV.getName()), IssueSeverity.ERROR); 436 } else if (propV.isChoice() && propV.getName().endsWith("[x]")) { 437 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_CANNOT_BE_KEYED_ARRAY_NO_CHOICE, propV.getName()), IssueSeverity.ERROR); 438 } else if (!(e instanceof JsonObject)) { 439 logError(errors, 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); 440 } else { 441 JsonObject o = (JsonObject) e; 442 if (o.isExtraComma()) { 443 logError(errors, "2022-11-26", o.getEnd().getLine(), o.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR); 444 } 445 446 int i = 0; 447 Set<String> names = new HashSet<>(); 448 for (JsonProperty pv : o.getProperties()) { 449 if (names.contains(pv.getName())) { 450 logError(errors, "2022-11-26", line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, pv.getName()), IssueSeverity.ERROR); 451 } else { 452 names.add(pv.getName()); 453 pv.setTag(1); 454 } 455 // create an array entry 456 String npathArr = path+"."+property.getName()+"["+i+"]"; 457 String fpathArr = element.getPath()+"."+property.getName()+"["+i+"]"; 458 459 Element n = new Element(name, property).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON); 460 n.setPath(fpath); 461 element.getChildren().add(n); 462 // handle the key 463 String fpathKey = fpathArr+"."+propK.getName(); 464 Element nKey = new Element(code, propK).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON); 465 checkComments(errors, pv.getValue(), n, fpathArr); 466 nKey.setPath(fpathKey); 467 n.getChildren().add(nKey); 468 nKey.setValue(pv.getName()); 469 470 471 boolean ok = true; 472 Property pvl = propV; 473 if (propV.isJsonPrimitiveChoice()) { 474 ok = false; 475 String type = getTypeFromJsonType(pv.getValue()); 476 if (type == null) { 477 logError(errors, ValidationMessage.NO_RULE_DATE, line(pv.getValue()), col(pv.getValue()), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE, describeType(pv.getValue()), propV.getName(), propV.typeSummary()), IssueSeverity.ERROR); 478 } else if (propV.hasType(type)) { 479 pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), type); 480 ok = true; 481 } else { 482 logError(errors, ValidationMessage.NO_RULE_DATE, line(pv.getValue()), col(pv.getValue()), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE_WRONG, describeType(pv.getValue()), propV.getName(), type, propV.typeSummary()), IssueSeverity.ERROR); 483 } 484 } 485 if (ok) { 486 // handle the value 487 String npathV = npathArr+"."+pvl.getName(); 488 String fpathV = fpathArr+"."+pvl.getName(); 489 if (propV.isPrimitive(pvl.getType(null))) { 490 parseChildPrimitiveInstance(errors, n, pvl, pvl.getName(), false, npathV, fpathV, pv.getValue(), null); 491 } else if (pv.getValue() instanceof JsonObject || pv.getValue() instanceof JsonNull) { 492 parseChildComplexInstance(errors, npathV, fpathV, n, pvl, pvl.getName(), pv.getValue(), null, null, null); 493 } else { 494 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(pv.getValue())), IssueSeverity.ERROR); 495 } 496 } 497 i++; 498 } 499 } 500 } 501 } else { 502 if (property.isList()) { 503 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(e), name, path), IssueSeverity.ERROR); 504 } 505 parseChildComplexInstance(errors, npath, fpath, element, property, name, e, null, null, null); 506 } 507 } 508 509 private Object propNames(List<Property> properties) { 510 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 511 for (Property p: properties) { 512 b.append(p.getName()); 513 } 514 return b.toString(); 515 } 516 517 private List<Property> parseChildComplexInstance(List<ValidationMessage> errors, String npath, String fpath, Element element, Property property, String name, JsonElement e, JsonElement commentContext, String commentPath, List<Property> properties) throws FHIRException { 518 if (property.hasTypeSpecifier()) { 519 FHIRPathEngine fpe = new FHIRPathEngine(context); 520 String type = null; 521 String cond = null; 522 for (StringPair sp : property.getTypeSpecifiers()) { 523 if (fpe.evaluateToBoolean(null, baseElement, baseElement, element, fpe.parse(sp.getName()))) { 524 type = sp.getValue(); 525 cond = sp.getName(); 526 break; 527 } 528 } 529 if (type != null) { 530 StructureDefinition sd = context.fetchResource(StructureDefinition.class, type); 531 if (sd == null) { 532 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ILLEGAL_TYPE, type, cond), IssueSeverity.ERROR); 533 } else { 534 if (sd.getAbstract()) { 535 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ABSTRACT_TYPE, type, cond), IssueSeverity.ERROR); 536 } 537 property = property.cloneToType(sd); 538 } 539 } else { 540 StructureDefinition sd = context.fetchTypeDefinition(property.getType()); 541 if (sd == null) { 542 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_NM_ILLEGAL_TYPE, property.getType()), IssueSeverity.ERROR); 543 } else if (sd.getAbstract()) { 544 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_NM_ABSTRACT_TYPE, property.getType()), IssueSeverity.ERROR); 545 } 546 } 547 } 548 if (e instanceof JsonObject) { 549 JsonObject child = (JsonObject) e; 550 Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON); 551 n.setPath(fpath); 552 checkComments(errors, commentContext, n, commentPath); 553 checkObject(errors, child, n, npath); 554 element.getChildren().add(n); 555 if (property.isResource()) { 556 parseResource(errors, npath, child, n, property); 557 } else { 558 return parseChildren(errors, npath, child, n, false, properties); 559 } 560 } else if (property.isNullable() && e instanceof JsonNull) { 561 // we create an element marked as a null element so we know something was present 562 JsonNull child = (JsonNull) e; 563 Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON); 564 checkComments(errors, commentContext, n, commentPath); 565 checkComments(errors, child, n, fpath); 566 n.setPath(fpath); 567 element.getChildren().add(n); 568 n.setNull(true); 569 // nothing to do, it's ok, but we treat it like it doesn't exist 570 } else { 571 logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE__NOT_, (property.isList() ? "an Array" : "an Object"), describe(e), name, npath), IssueSeverity.ERROR); 572 } 573 return null; 574 } 575 576 private String describe(JsonElement e) { 577 if (e instanceof JsonArray) { 578 return "an Array"; 579 } 580 if (e instanceof JsonObject) { 581 return "an Object"; 582 } 583 if (e instanceof JsonNull) { 584 return "a Null"; 585 } 586 if (e instanceof JsonPrimitive) { 587 return "a Primitive property"; 588 } 589 return null; 590 } 591 592 private String describeType(JsonElement e) { 593 return e.type().toName(); 594 } 595 596// JsonProperty main = children.containsKey(name) ? children.get(name) : null; 597// JsonProperty fork = children.containsKey("_"+name) ? children.get("_"+name) : null; 598 private void parseChildPrimitive(List<ValidationMessage> errors, JsonProperty main, JsonProperty fork, Element element, Property property, String path, String name, boolean isJsonName) throws FHIRException { 599 String npath = path+"."+property.getName(); 600 String fpath = element.getPath()+"."+property.getName(); 601 if (main != null) { main.setTag(1); } 602 if (fork != null) { fork.setTag(1); } 603 604 if (main != null && main.getValue().isJsonString() && main.isUnquotedValue()) { 605 logError(errors, "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); 606 } 607 if (main != null || fork != null) { 608 if (property.isList()) { 609 boolean ok = true; 610 if (!(main == null || main.getValue() instanceof JsonArray)) { 611 logError(errors, 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); 612 ok = false; 613 } 614 if (!(fork == null || fork.getValue() instanceof JsonArray)) { 615 logError(errors, 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); 616 ok = false; 617 } 618 if (ok) { 619 JsonArray arr1 = (JsonArray) (main == null ? null : main.getValue()); 620 JsonArray arr2 = (JsonArray) (fork == null ? null : fork.getValue()); 621 if (arr1 != null && arr1.isExtraComma()) { 622 logError(errors, "2022-11-26", arr1.getEnd().getLine(), arr1.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR); 623 } 624 if (arr2 != null && arr2.isExtraComma()) { 625 logError(errors, "2022-11-26", arr2.getEnd().getLine(), arr2.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR); 626 } 627 628 for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) { 629 JsonElement m = arrI(arr1, i); 630 JsonElement f = arrI(arr2, i); 631 if (m != null && m.isJsonString() && arr1.isUnquoted(i)) { 632 logError(errors, "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); 633 } 634 parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, m, f); 635 } 636 } 637 } else { 638 parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, main == null ? null : main.getValue(), fork == null ? null : fork.getValue()); 639 } 640 } 641 } 642 643 private JsonElement arrI(JsonArray arr, int i) { 644 return arr == null || i >= arr.size() || arr.get(i) instanceof JsonNull ? null : arr.get(i); 645 } 646 647 private int arrC(JsonArray arr) { 648 return arr == null ? 0 : arr.size(); 649 } 650 651 private void parseChildPrimitiveInstance(List<ValidationMessage> errors, Element element, Property property, String name, boolean isJsonName, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException { 652 if (main != null && !(main.isJsonBoolean() || main.isJsonNumber() || main.isJsonString())) { 653 logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage( 654 I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR); 655 } else if (fork != null && !(fork instanceof JsonObject)) { 656 logError(errors, 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); 657 } else { 658 Element n = new Element(isJsonName ? property.getName() : name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork)).setFormat(FhirFormat.JSON); 659 if (main != null) { 660 checkComments(errors, main, n, npath); 661 } 662 if (fork != null) { 663 checkComments(errors, fork, n, npath); 664 } 665 n.setPath(fpath); 666 element.getChildren().add(n); 667 if (main != null) { 668 JsonPrimitive p = (JsonPrimitive) main; 669 n.setValue(property.hasImpliedPrefix() ? property.getImpliedPrefix()+p.asString() : p.asString()); 670 if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) { 671 try { 672 XhtmlParser xhtml = new XhtmlParser(); 673 n.setXhtml(xhtml.setXmlMode(true).parse(n.getValue(), null).getDocumentElement()); 674 if (policy == ValidationPolicy.EVERYTHING) { 675 for (StringPair s : xhtml.getValidationIssues()) { 676 logError(errors, "2022-11-17", line(main), col(main), npath, IssueType.INVALID, context.formatMessage(s.getName(), s.getValue()), IssueSeverity.ERROR); 677 } 678 } 679 } catch (Exception e) { 680 logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_XHTML_, e.getMessage()), IssueSeverity.ERROR); 681 } 682 } 683 if (policy == ValidationPolicy.EVERYTHING) { 684 // now we cross-check the primitive format against the stated type 685 if (Utilities.existsInList(n.getType(), "boolean")) { 686 if (!p.isJsonBoolean()) { 687 logError(errors, 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); 688 } 689 } else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) { 690 if (!p.isJsonNumber()) 691 logError(errors, 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); 692 } else if (!p.isJsonString()) { 693 logError(errors, 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); 694 } 695 } 696 } 697 if (fork != null) { 698 JsonObject child = (JsonObject) fork; 699 checkObject(errors, child, n, npath); 700 parseChildren(errors, npath, child, n, false, null); 701 } 702 } 703 } 704 705 706 private void parseResource(List<ValidationMessage> errors, String npath, JsonObject res, Element parent, Property elementProperty) throws FHIRException { 707 JsonElement rt = res.get("resourceType"); 708 if (rt == null) { 709 logError(errors, ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL); 710 } else if (!rt.isJsonString()) { 711 logError(errors, "2022-11-26", line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL); 712 } else { 713 String name = rt.asString(); 714 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null)); 715 if (sd == null) { 716 logError(errors, 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); 717 } else { 718 parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities()), SpecialElement.fromProperty(parent.getProperty()), elementProperty); 719 parent.setType(name); 720 parseChildren(errors, npath, res, parent, true, null); 721 } 722 } 723 if (res.isExtraComma()) { 724 logError(errors, "2022-11-26", res.getEnd().getLine(), res.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR); 725 } 726 727 } 728 729 private int line(JsonElement e) { 730 return e.getStart().getLine(); 731 } 732 733 private int col(JsonElement e) { 734 return e.getEnd().getCol(); 735 } 736 737 738 protected void prop(String name, String value, String link) throws IOException { 739 json.link(link); 740 if (name != null) 741 json.name(name); 742 json.value(value); 743 } 744 745 protected void open(String name, String link) throws IOException { 746 json.link(link); 747 if (name != null) 748 json.name(name); 749 json.beginObject(); 750 } 751 752 protected void close() throws IOException { 753 json.endObject(); 754 } 755 756 protected void openArray(String name, String link) throws IOException { 757 json.link(link); 758 if (name != null) 759 json.name(name); 760 json.beginArray(); 761 } 762 763 protected void closeArray() throws IOException { 764 json.endArray(); 765 } 766 767 768 @Override 769 public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException { 770 if (e.getPath() == null) { 771 e.populatePaths(null); 772 } 773 774 markedXhtml = false; 775 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 776 if (style == OutputStyle.CANONICAL) { 777 json = new JsonCreatorCanonical(osw); 778 } else if (style == OutputStyle.PRETTY) { 779 json = new JsonCreatorDirect(osw, true, allowComments); 780 } else { 781 json = new JsonCreatorDirect(osw, false, allowComments); 782 } 783 checkComposeComments(e); 784 json.beginObject(); 785 prop("resourceType", e.getType(), null); 786 Set<String> done = new HashSet<String>(); 787 for (Element child : e.getChildren()) { 788 compose(e.getName(), e, done, child); 789 } 790 json.endObject(); 791 json.finish(); 792 osw.flush(); 793 } 794 795 private void checkComposeComments(Element e) { 796 for (String s : e.getComments()) { 797 json.comment(s); 798 } 799 } 800 801 public void compose(Element e, JsonCreator json) throws Exception { 802 if (e.getPath() == null) { 803 e.populatePaths(null); 804 } 805 806 this.json = json; 807 checkComposeComments(e); 808 json.beginObject(); 809 810 prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty())); 811 Set<String> done = new HashSet<String>(); 812 for (Element child : e.getChildren()) { 813 compose(e.getName(), e, done, child); 814 } 815 json.endObject(); 816 json.finish(); 817 } 818 819 private void compose(String path, Element e, Set<String> done, Element child) throws IOException { 820 checkComposeComments(child); 821 if (wantCompose(path, child)) { 822 boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList(); 823 if (!isList) {// for specials, ignore the cardinality of the stated type 824 compose(path, child); 825 } else if (!done.contains(child.getName())) { 826 done.add(child.getName()); 827 List<Element> list = e.getChildrenByName(child.getName()); 828 composeList(path, list); 829 } 830 } 831 } 832 833 834 private void composeList(String path, List<Element> list) throws IOException { 835 // there will be at least one element 836 String name = list.get(0).getName(); 837 boolean complex = true; 838 if (list.get(0).isPrimitive()) { 839 boolean prim = false; 840 complex = false; 841 for (Element item : list) { 842 if (item.hasValue()) 843 prim = true; 844 if (item.hasChildren()) 845 complex = true; 846 } 847 if (prim) { 848 openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty())); 849 for (Element item : list) { 850 if (item.hasValue()) { 851 if (linkResolver != null && item.getProperty().isReference()) { 852 String ref = linkResolver.resolveReference(getReferenceForElement(item)); 853 if (ref != null) { 854 json.externalLink(ref); 855 } 856 } 857 primitiveValue(null, item); 858 } else 859 json.nullValue(); 860 } 861 closeArray(); 862 } 863 name = "_"+name; 864 } 865 if (complex) { 866 openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty())); 867 int i = 0; 868 for (Element item : list) { 869 if (item.hasChildren()) { 870 open(null,null); 871 if (item.getProperty().isResource()) { 872 prop("resourceType", item.getType(), linkResolver == null ? null : linkResolver.resolveType(item.getType())); 873 } 874 if (linkResolver != null && item.getProperty().isReference()) { 875 String ref = linkResolver.resolveReference(getReferenceForElement(item)); 876 if (ref != null) { 877 json.externalLink(ref); 878 } 879 } 880 Set<String> done = new HashSet<String>(); 881 for (Element child : item.getChildren()) { 882 compose(path+"."+name+"[]", item, done, child); 883 } 884 close(); 885 } else { 886 json.nullValue(); 887 } 888 i++; 889 } 890 closeArray(); 891 } 892 } 893 894 private void primitiveValue(String name, Element item) throws IOException { 895 if (name != null) { 896 if (linkResolver != null) 897 json.link(linkResolver.resolveProperty(item.getProperty())); 898 json.name(name); 899 } 900 String type = item.getType(); 901 if (Utilities.existsInList(type, "boolean")) 902 json.value(item.getValue().trim().equals("true") ? new Boolean(true) : new Boolean(false)); 903 else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt")) 904 json.value(new Integer(item.getValue())); 905 else if (Utilities.existsInList(type, "decimal")) 906 try { 907 json.value(new BigDecimal(item.getValue())); 908 } catch (Exception e) { 909 throw new NumberFormatException(context.formatMessage(I18nConstants.ERROR_WRITING_NUMBER__TO_JSON, item.getValue())); 910 } 911 else 912 json.value(item.getValue()); 913 } 914 915 private void compose(String path, Element element) throws IOException { 916 String name = element.getName(); 917 if (element.isPrimitive() || isPrimitive(element.getType())) { 918 if (element.hasValue()) 919 primitiveValue(name, element); 920 name = "_"+name; 921 if (!markedXhtml && element.getType().equals("xhtml")) 922 json.anchor("end-xhtml"); 923 markedXhtml = true; 924 } 925 if (element.hasChildren()) { 926 open(name, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); 927 if (element.getProperty().isResource()) { 928 prop("resourceType", element.getType(), linkResolver == null ? null : linkResolver.resolveType(element.getType())); 929 } 930 if (linkResolver != null && element.getProperty().isReference()) { 931 String ref = linkResolver.resolveReference(getReferenceForElement(element)); 932 if (ref != null) { 933 json.externalLink(ref); 934 } 935 } 936 Set<String> done = new HashSet<String>(); 937 for (Element child : element.getChildren()) { 938 compose(path+"."+element.getName(), element, done, child); 939 } 940 close(); 941 } 942 } 943 944 945 public boolean isAllowComments() { 946 return allowComments; 947 } 948 949 public JsonParser setAllowComments(boolean allowComments) { 950 this.allowComments = allowComments; 951 return this; 952 } 953 954 955}