001package org.hl7.fhir.r5.elementmodel; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.IOException; 035import java.io.InputStream; 036import java.io.OutputStream; 037import java.io.OutputStreamWriter; 038import java.math.BigDecimal; 039import java.util.ArrayList; 040import java.util.HashSet; 041import java.util.IdentityHashMap; 042import java.util.List; 043import java.util.Map; 044import java.util.Map.Entry; 045import java.util.Set; 046 047import org.hl7.fhir.exceptions.FHIRException; 048import org.hl7.fhir.exceptions.FHIRFormatError; 049import org.hl7.fhir.r5.conformance.ProfileUtilities; 050import org.hl7.fhir.r5.context.IWorkerContext; 051import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; 052import org.hl7.fhir.r5.formats.IParser.OutputStyle; 053import org.hl7.fhir.r5.formats.JsonCreator; 054import org.hl7.fhir.r5.formats.JsonCreatorCanonical; 055import org.hl7.fhir.r5.formats.JsonCreatorGson; 056import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 057import org.hl7.fhir.r5.utils.FHIRPathEngine; 058import org.hl7.fhir.r5.model.StructureDefinition; 059import org.hl7.fhir.utilities.TextFile; 060import org.hl7.fhir.utilities.Utilities; 061import org.hl7.fhir.utilities.i18n.I18nConstants; 062import org.hl7.fhir.utilities.json.JsonTrackingParser; 063import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData; 064import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 065import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 066import org.hl7.fhir.utilities.xhtml.XhtmlParser; 067 068import com.google.gson.JsonArray; 069import com.google.gson.JsonElement; 070import com.google.gson.JsonNull; 071import com.google.gson.JsonObject; 072import com.google.gson.JsonPrimitive; 073 074public class JsonParser extends ParserBase { 075 076 private JsonCreator json; 077 private Map<JsonElement, LocationData> map; 078 private boolean allowComments; 079 080 private ProfileUtilities profileUtilities; 081 082 public JsonParser(IWorkerContext context, ProfileUtilities utilities) { 083 super(context); 084 085 this.profileUtilities = utilities; 086 } 087 088 public JsonParser(IWorkerContext context) { 089 super(context); 090 091 this.profileUtilities = new ProfileUtilities(this.context, null, null, new FHIRPathEngine(context)); 092 } 093 094 public Element parse(String source, String type) throws Exception { 095 JsonObject obj = (JsonObject) new com.google.gson.JsonParser().parse(source); 096 String path = "/"+type; 097 StructureDefinition sd = getDefinition(-1, -1, type); 098 if (sd == null) 099 return null; 100 101 Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities)); 102 result.setPath(type); 103 checkObject(obj, path); 104 result.setType(type); 105 parseChildren(path, obj, result, true); 106 result.numberChildren(); 107 return result; 108 } 109 110 111 @Override 112 public List<NamedElement> parse(InputStream stream) throws IOException, FHIRException { 113 // if we're parsing at this point, then we're going to use the custom parser 114 List<NamedElement> res = new ArrayList<>(); 115 map = new IdentityHashMap<JsonElement, LocationData>(); 116 String source = TextFile.streamToString(stream); 117 if (policy == ValidationPolicy.EVERYTHING) { 118 JsonObject obj = null; 119 try { 120 obj = JsonTrackingParser.parse(source, map, false, allowComments); 121 } catch (Exception e) { 122 logError(-1, -1,context.formatMessage(I18nConstants.DOCUMENT), IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL); 123 return null; 124 } 125 assert (map.containsKey(obj)); 126 Element e = parse(obj); 127 if (e != null) { 128 res.add(new NamedElement(null, e)); 129 } 130 } else { 131 JsonObject obj = JsonTrackingParser.parse(source, null); // (JsonObject) new com.google.gson.JsonParser().parse(source); 132 // assert (map.containsKey(obj)); 133 Element e = parse(obj); 134 if (e != null) { 135 res.add(new NamedElement(null, e)); 136 } 137 } 138 return res; 139 } 140 141 public Element parse(JsonObject object, Map<JsonElement, LocationData> map) throws FHIRException { 142 this.map = map; 143 return parse(object); 144 } 145 146 public Element parse(JsonObject object) throws FHIRException { 147 JsonElement rt = object.get("resourceType"); 148 if (rt == null) { 149 logError(line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL); 150 return null; 151 } else { 152 String name = rt.getAsString(); 153 String path = name; 154 155 StructureDefinition sd = getDefinition(line(object), col(object), name); 156 if (sd == null) 157 return null; 158 159 Element result = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities)); 160 checkObject(object, path); 161 result.markLocation(line(object), col(object)); 162 result.setType(name); 163 result.setPath(result.fhirType()); 164 parseChildren(path, object, result, true); 165 result.numberChildren(); 166 return result; 167 } 168 } 169 170 private void checkObject(JsonObject object, String path) throws FHIRFormatError { 171 if (policy == ValidationPolicy.EVERYTHING) { 172 boolean found = false; 173 for (Entry<String, JsonElement> e : object.entrySet()) { 174 // if (!e.getKey().equals("fhir_comments")) { 175 found = true; 176 break; 177 // } 178 } 179 if (!found) 180 logError(line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR); 181 } 182 } 183 184 private void parseChildren(String path, JsonObject object, Element element, boolean hasResourceType) throws FHIRException { 185 reapComments(object, element); 186 List<Property> properties = element.getProperty().getChildProperties(element.getName(), null); 187 Set<String> processed = new HashSet<String>(); 188 if (hasResourceType) 189 processed.add("resourceType"); 190 191 // note that we do not trouble ourselves to maintain the wire format order here - we don't even know what it was anyway 192 // first pass: process the properties 193 for (Property property : properties) { 194 parseChildItem(path, object, element, processed, property); 195 } 196 197 // second pass: check for things not processed 198 if (policy != ValidationPolicy.NONE) { 199 for (Entry<String, JsonElement> e : object.entrySet()) { 200 if (!processed.contains(e.getKey())) { 201 logError(line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getKey()), IssueSeverity.ERROR); 202 } 203 } 204 } 205 } 206 207 public void parseChildItem(String path, JsonObject object, Element context, Set<String> processed, Property property) { 208 if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) { 209 for (TypeRefComponent type : property.getDefinition().getType()) { 210 String eName = property.getName().substring(0, property.getName().length()-3) + Utilities.capitalize(type.getWorkingCode()); 211 if (!isPrimitive(type.getWorkingCode()) && object.has(eName)) { 212 parseChildComplex(path, object, context, processed, property, eName); 213 break; 214 } else if (isPrimitive(type.getWorkingCode()) && (object.has(eName) || object.has("_"+eName))) { 215 parseChildPrimitive(object, context, processed, property, path, eName); 216 break; 217 } 218 } 219 } else if (property.isPrimitive(property.getType(null))) { 220 parseChildPrimitive(object, context, processed, property, path, property.getName()); 221 } else if (object.has(property.getName())) { 222 parseChildComplex(path, object, context, processed, property, property.getName()); 223 } 224 } 225 226 private void parseChildComplex(String path, JsonObject object, Element element, Set<String> processed, Property property, String name) throws FHIRException { 227 processed.add(name); 228 String npath = path+"."+property.getName(); 229 String fpath = element.getPath()+"."+property.getName(); 230 JsonElement e = object.get(name); 231 if (property.isList() && (e instanceof JsonArray)) { 232 JsonArray arr = (JsonArray) e; 233 if (arr.size() == 0) { 234 logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR); 235 } 236 int c = 0; 237 for (JsonElement am : arr) { 238 parseChildComplexInstance(npath+"["+c+"]", fpath+"["+c+"]", object, element, property, name, am); 239 c++; 240 } 241 } else { 242 if (property.isList()) { 243 logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describeType(e), name, path), IssueSeverity.ERROR); 244 } 245 parseChildComplexInstance(npath, fpath, object, element, property, name, e); 246 } 247 } 248 249 private String describeType(JsonElement e) { 250 if (e.isJsonArray()) 251 return "an Array"; 252 if (e.isJsonObject()) 253 return "an Object"; 254 if (e.isJsonPrimitive()) 255 return "a primitive property"; 256 if (e.isJsonNull()) 257 return "a Null"; 258 return null; 259 } 260 261 private void parseChildComplexInstance(String npath, String fpath, JsonObject object, Element element, Property property, String name, JsonElement e) throws FHIRException { 262 if (e instanceof JsonObject) { 263 JsonObject child = (JsonObject) e; 264 Element n = new Element(name, property).markLocation(line(child), col(child)); 265 n.setPath(fpath); 266 checkObject(child, npath); 267 element.getChildren().add(n); 268 if (property.isResource()) 269 parseResource(npath, child, n, property); 270 else 271 parseChildren(npath, child, n, false); 272 } else 273 logError(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); 274 } 275 276 private String describe(JsonElement e) { 277 if (e instanceof JsonArray) { 278 return "an array"; 279 } 280 if (e instanceof JsonObject) { 281 return "an object"; 282 } 283 if (e instanceof JsonNull) { 284 return "null"; 285 } 286 return "a primitive property"; 287 } 288 289 private void parseChildPrimitive(JsonObject object, Element element, Set<String> processed, Property property, String path, String name) throws FHIRException { 290 String npath = path+"."+property.getName(); 291 String fpath = element.getPath()+"."+property.getName(); 292 processed.add(name); 293 processed.add("_"+name); 294 JsonElement main = object.has(name) ? object.get(name) : null; 295 JsonElement fork = object.has("_"+name) ? object.get("_"+name) : null; 296 if (main != null || fork != null) { 297 if (property.isList()) { 298 boolean ok = true; 299 if (!(main == null || main instanceof JsonArray)) { 300 logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR); 301 ok = false; 302 } 303 if (!(fork == null || fork instanceof JsonArray)) { 304 logError(line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_BASE_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR); 305 ok = false; 306 } 307 if (ok) { 308 JsonArray arr1 = (JsonArray) main; 309 JsonArray arr2 = (JsonArray) fork; 310 for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) { 311 JsonElement m = arrI(arr1, i); 312 JsonElement f = arrI(arr2, i); 313 parseChildPrimitiveInstance(element, property, name, npath, fpath, m, f); 314 } 315 } 316 } else { 317 parseChildPrimitiveInstance(element, property, name, npath, fpath, main, fork); 318 } 319 } 320 } 321 322 private JsonElement arrI(JsonArray arr, int i) { 323 return arr == null || i >= arr.size() || arr.get(i) instanceof JsonNull ? null : arr.get(i); 324 } 325 326 private int arrC(JsonArray arr) { 327 return arr == null ? 0 : arr.size(); 328 } 329 330 private void parseChildPrimitiveInstance(Element element, Property property, String name, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException { 331 if (main != null && !(main instanceof JsonPrimitive)) 332 logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage( 333 I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR); 334 else if (fork != null && !(fork instanceof JsonObject)) 335 logError(line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(fork), name, npath), IssueSeverity.ERROR); 336 else { 337 Element n = new Element(name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork)); 338 n.setPath(fpath); 339 element.getChildren().add(n); 340 if (main != null) { 341 JsonPrimitive p = (JsonPrimitive) main; 342 if (p.isNumber() && p.getAsNumber() instanceof JsonTrackingParser.PresentedBigDecimal) { 343 String rawValue = ((JsonTrackingParser.PresentedBigDecimal) p.getAsNumber()).getPresentation(); 344 n.setValue(rawValue); 345 } else { 346 n.setValue(p.getAsString()); 347 } 348 if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) { 349 try { 350 n.setXhtml(new XhtmlParser().setValidatorMode(policy == ValidationPolicy.EVERYTHING).parse(n.getValue(), null).getDocumentElement()); 351 } catch (Exception e) { 352 logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_XHTML_, e.getMessage()), IssueSeverity.ERROR); 353 } 354 } 355 if (policy == ValidationPolicy.EVERYTHING) { 356 // now we cross-check the primitive format against the stated type 357 if (Utilities.existsInList(n.getType(), "boolean")) { 358 if (!p.isBoolean()) 359 logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_BOOLEAN), IssueSeverity.ERROR); 360 } else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) { 361 if (!p.isNumber()) 362 logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_NUMBER), IssueSeverity.ERROR); 363 } else if (!p.isString()) 364 logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_STRING), IssueSeverity.ERROR); 365 } 366 } 367 if (fork != null) { 368 JsonObject child = (JsonObject) fork; 369 checkObject(child, npath); 370 parseChildren(npath, child, n, false); 371 } 372 } 373 } 374 375 376 private void parseResource(String npath, JsonObject res, Element parent, Property elementProperty) throws FHIRException { 377 JsonElement rt = res.get("resourceType"); 378 if (rt == null) { 379 logError(line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL); 380 } else { 381 String name = rt.getAsString(); 382 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, context.getOverrideVersionNs())); 383 if (sd == null) { 384 logError(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); 385 } else { 386 parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities), SpecialElement.fromProperty(parent.getProperty()), elementProperty); 387 parent.setType(name); 388 parseChildren(npath, res, parent, true); 389 } 390 } 391 } 392 393 private void reapComments(JsonObject object, Element context) { 394 if (object.has("fhir_comments")) { 395 JsonArray arr = object.getAsJsonArray("fhir_comments"); 396 for (JsonElement e : arr) { 397 context.getComments().add(e.getAsString()); 398 } 399 } 400 } 401 402 private int line(JsonElement e) { 403 if (map == null|| !map.containsKey(e)) 404 return -1; 405 else 406 return map.get(e).getLine(); 407 } 408 409 private int col(JsonElement e) { 410 if (map == null|| !map.containsKey(e)) 411 return -1; 412 else 413 return map.get(e).getCol(); 414 } 415 416 417 protected void prop(String name, String value, String link) throws IOException { 418 json.link(link); 419 if (name != null) 420 json.name(name); 421 json.value(value); 422 } 423 424 protected void open(String name, String link) throws IOException { 425 json.link(link); 426 if (name != null) 427 json.name(name); 428 json.beginObject(); 429 } 430 431 protected void close() throws IOException { 432 json.endObject(); 433 } 434 435 protected void openArray(String name, String link) throws IOException { 436 json.link(link); 437 if (name != null) 438 json.name(name); 439 json.beginArray(); 440 } 441 442 protected void closeArray() throws IOException { 443 json.endArray(); 444 } 445 446 447 @Override 448 public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException { 449 OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); 450 if (style == OutputStyle.CANONICAL) 451 json = new JsonCreatorCanonical(osw); 452 else 453 json = new JsonCreatorGson(osw); 454 json.setIndent(style == OutputStyle.PRETTY ? " " : ""); 455 json.beginObject(); 456 prop("resourceType", e.getType(), null); 457 Set<String> done = new HashSet<String>(); 458 for (Element child : e.getChildren()) { 459 compose(e.getName(), e, done, child); 460 } 461 json.endObject(); 462 json.finish(); 463 osw.flush(); 464 } 465 466 public void compose(Element e, JsonCreator json) throws Exception { 467 this.json = json; 468 json.beginObject(); 469 470 prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty())); 471 Set<String> done = new HashSet<String>(); 472 for (Element child : e.getChildren()) { 473 compose(e.getName(), e, done, child); 474 } 475 json.endObject(); 476 json.finish(); 477 } 478 479 private void compose(String path, Element e, Set<String> done, Element child) throws IOException { 480 boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList(); 481 if (!isList) {// for specials, ignore the cardinality of the stated type 482 compose(path, child); 483 } else if (!done.contains(child.getName())) { 484 done.add(child.getName()); 485 List<Element> list = e.getChildrenByName(child.getName()); 486 composeList(path, list); 487 } 488 } 489 490 private void composeList(String path, List<Element> list) throws IOException { 491 // there will be at least one element 492 String name = list.get(0).getName(); 493 boolean complex = true; 494 if (list.get(0).isPrimitive()) { 495 boolean prim = false; 496 complex = false; 497 for (Element item : list) { 498 if (item.hasValue()) 499 prim = true; 500 if (item.hasChildren()) 501 complex = true; 502 } 503 if (prim) { 504 openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty())); 505 for (Element item : list) { 506 if (item.hasValue()) 507 primitiveValue(null, item); 508 else 509 json.nullValue(); 510 } 511 closeArray(); 512 } 513 name = "_"+name; 514 } 515 if (complex) { 516 openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty())); 517 for (Element item : list) { 518 if (item.hasChildren()) { 519 open(null,null); 520 if (item.getProperty().isResource()) { 521 prop("resourceType", item.getType(), linkResolver == null ? null : linkResolver.resolveType(item.getType())); 522 } 523 Set<String> done = new HashSet<String>(); 524 for (Element child : item.getChildren()) { 525 compose(path+"."+name+"[]", item, done, child); 526 } 527 close(); 528 } else 529 json.nullValue(); 530 } 531 closeArray(); 532 } 533 } 534 535 private void primitiveValue(String name, Element item) throws IOException { 536 if (name != null) { 537 if (linkResolver != null) 538 json.link(linkResolver.resolveProperty(item.getProperty())); 539 json.name(name); 540 } 541 String type = item.getType(); 542 if (Utilities.existsInList(type, "boolean")) 543 json.value(item.getValue().trim().equals("true") ? new Boolean(true) : new Boolean(false)); 544 else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt")) 545 json.value(new Integer(item.getValue())); 546 else if (Utilities.existsInList(type, "decimal")) 547 try { 548 json.value(new BigDecimal(item.getValue())); 549 } catch (Exception e) { 550 throw new NumberFormatException(context.formatMessage(I18nConstants.ERROR_WRITING_NUMBER__TO_JSON, item.getValue())); 551 } 552 else 553 json.value(item.getValue()); 554 } 555 556 private void compose(String path, Element element) throws IOException { 557 String name = element.getName(); 558 if (element.isPrimitive() || isPrimitive(element.getType())) { 559 if (element.hasValue()) 560 primitiveValue(name, element); 561 name = "_"+name; 562 if (element.getType().equals("xhtml")) 563 json.anchor("end-xhtml"); 564 } 565 if (element.hasChildren()) { 566 open(name, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); 567 if (element.getProperty().isResource()) { 568 prop("resourceType", element.getType(), linkResolver == null ? null : linkResolver.resolveType(element.getType())); 569 } 570 Set<String> done = new HashSet<String>(); 571 for (Element child : element.getChildren()) { 572 compose(path+"."+element.getName(), element, done, child); 573 } 574 close(); 575 } 576 } 577 578 public boolean isAllowComments() { 579 return allowComments; 580 } 581 582 public JsonParser setAllowComments(boolean allowComments) { 583 this.allowComments = allowComments; 584 return this; 585 } 586 587 588}