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.JsonParser.ILogicalModelResolver;
057import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
058import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
059import org.hl7.fhir.r5.formats.IParser.OutputStyle;
060import org.hl7.fhir.r5.formats.JsonCreator;
061import org.hl7.fhir.r5.formats.JsonCreatorCanonical;
062import org.hl7.fhir.r5.formats.JsonCreatorDirect;
063import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
064import org.hl7.fhir.r5.model.ElementDefinition;
065import org.hl7.fhir.r5.model.StructureDefinition;
066import org.hl7.fhir.r5.utils.ToolingExtensions;
067import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
068import org.hl7.fhir.utilities.StringPair;
069import org.hl7.fhir.utilities.FileUtilities;
070import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
071import org.hl7.fhir.utilities.Utilities;
072import org.hl7.fhir.utilities.VersionUtilities;
073import org.hl7.fhir.utilities.i18n.I18nConstants;
074import org.hl7.fhir.utilities.json.model.JsonArray;
075import org.hl7.fhir.utilities.json.model.JsonComment;
076import org.hl7.fhir.utilities.json.model.JsonElement;
077import org.hl7.fhir.utilities.json.model.JsonNull;
078import org.hl7.fhir.utilities.json.model.JsonObject;
079import org.hl7.fhir.utilities.json.model.JsonPrimitive;
080import org.hl7.fhir.utilities.json.model.JsonProperty;
081import org.hl7.fhir.utilities.validation.ValidationMessage;
082import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
083import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
084import org.hl7.fhir.utilities.xhtml.XhtmlParser;
085
086
087@MarkedToMoveToAdjunctPackage
088public class JsonParser extends ParserBase {
089
090  public interface ILogicalModelResolver {
091
092    StructureDefinition resolve(JsonObject object);
093
094  }
095
096  private JsonCreator json;
097  private boolean allowComments;
098  private boolean elideElements;
099//  private boolean suppressResourceType;
100
101  private Element baseElement;
102  private boolean markedXhtml;
103  private ILogicalModelResolver logicalModelResolver;
104
105  public JsonParser(IWorkerContext context, ProfileUtilities utilities) {
106    super(context, utilities);
107
108  }
109
110  public JsonParser(IWorkerContext context) {
111    super(context);
112  }
113
114  public Element parse(String source, String type) throws Exception {
115    return parse(source, type, false);
116  }
117  
118  public Element parse(String source, String type, boolean inner) throws Exception {
119    ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", source.getBytes(StandardCharsets.UTF_8), false);
120    JsonObject obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true); 
121    String path = "/"+type;
122    StructureDefinition sd = getDefinition(focusFragment.getErrors(), -1, -1, type);
123    if (sd == null) {
124      return null;
125    }
126
127    if (inner) {
128      // we have an anonymous wrapper that has an arbitrarily named property with the specified type. We're going to invent a snapshot for that 
129      sd = new StructureDefinition();
130      sd.setType("Wrapper");
131      ElementDefinition bEd = sd.getSnapshot().addElement();
132      ElementDefinition nEd = sd.getSnapshot().addElement();
133      bEd.setPath("Wrapper");
134      nEd.setPath("Wrapper."+obj.getProperties().get(0).getName());
135      nEd.addType().setCode(type);
136      nEd.setMax(obj.getProperties().get(0).getValue().isJsonArray() ? "*" : "1"); 
137    }
138    Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON);
139    result.setPath(type);
140    checkObject(focusFragment.getErrors(), obj, result, path);
141    result.setType(type);
142    parseChildren(focusFragment.getErrors(), path, obj, result, true, null);
143    result.numberChildren();
144    return result;
145  }
146
147
148  @Override
149  public List<ValidatedFragment> parse(InputStream inStream) throws IOException, FHIRException {
150    return parse(inStream, 0);
151  }
152  
153  public List<ValidatedFragment> parse(InputStream inStream, int line) throws IOException, FHIRException {
154//    long start = System.currentTimeMillis();
155    byte[] content = FileUtilities.streamToBytes(inStream);
156    ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", content, false);
157    
158    ByteArrayInputStream stream = new ByteArrayInputStream(content);
159    
160    // if we're parsing at this point, then we're going to use the custom parser
161    String source = FileUtilities.streamToString(stream);
162    JsonObject obj = null;
163    
164    if (policy == ValidationPolicy.EVERYTHING) {
165      try {
166        obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line); 
167      } catch (Exception e) {
168        logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, -1, -1, null, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL);
169      }
170    } else {
171      obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line); 
172    }
173    
174    if (obj != null) {
175      focusFragment.setElement(parse(focusFragment.getErrors(), obj));
176    }
177    List<ValidatedFragment> res = new ArrayList<>();
178    res.add(focusFragment);
179
180    return res;
181  }
182
183  public Element parse(List<ValidationMessage> errors, JsonObject object) throws FHIRException {
184    return parse(errors, object, null);
185  }
186  
187  private StructureDefinition resolveLogical(JsonObject object) {
188    StructureDefinition sd = getLogical();
189    if (sd != null) {
190      return sd;
191    } else if (logicalModelResolver != null) {
192      return logicalModelResolver.resolve(object);
193    } else {
194      return null;
195    }
196  }
197  
198  public Element parse(List<ValidationMessage> errors, JsonObject object, String statedPath) throws FHIRException {
199    StructureDefinition sd = resolveLogical(object);
200    String name;
201    String path;      
202    if (sd == null) {
203      JsonElement rt = object.get("resourceType");
204      if (rt == null) {
205        logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
206        return null;
207      } else if (!rt.isJsonString()) {
208        logError(errors, "2022-11-26", line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL);
209        return null;
210      } else {
211        name = rt.asString();
212
213        sd = getDefinition(errors, line(object), col(object), name);
214        if (sd == null) {
215         return null;
216        }
217      }
218      path = statedPath == null ? name : statedPath;
219    } else {
220      name = sd.getType();
221      path = statedPath == null ? sd.getTypeTail() : statedPath;
222    }
223    baseElement = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities())).setFormat(FhirFormat.JSON);
224    checkObject(errors, object, baseElement, path);
225    baseElement.markLocation(line(object), col(object));
226    baseElement.setType(name);
227    baseElement.setPath(statedPath == null ? baseElement.fhirTypeRoot() : statedPath);
228    parseChildren(errors, path, object, baseElement, true, null);
229    baseElement.numberChildren();
230    return baseElement;
231  }
232
233  private void checkObject(List<ValidationMessage> errors, JsonObject object, Element b, String path) {
234    b.setNativeObject(object);
235    checkComments(errors, object, b, path);
236    if (policy == ValidationPolicy.EVERYTHING) {
237      if (object.getProperties().size() == 0) {
238        logError(errors, ValidationMessage.NO_RULE_DATE, line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
239      }
240    }    
241  }
242
243  private void checkComments(List<ValidationMessage> errors, JsonElement element, Element b, String path) throws FHIRFormatError {
244    if (element != null && element.hasComments()) {
245      if (allowComments) {
246        for (JsonComment c : element.getComments()) {
247          b.getComments().add(c.getContent());
248        }
249      } else {
250        for (JsonComment c : element.getComments()) {
251          logError(errors, "2022-11-26", c.getStart().getLine(), c.getStart().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMENTS_NOT_ALLOWED), IssueSeverity.ERROR);
252        }        
253      }
254    }
255  }
256
257  private List<Property> parseChildren(List<ValidationMessage> errors, String path, JsonObject object, Element element, boolean hasResourceType, List<Property> properties) throws FHIRException {
258    if (properties == null) {
259      // save time refetching these if we're in a loop
260      properties = element.getProperty().getChildProperties(element.getName(), null);
261    }
262    processChildren(errors, path, object);
263
264    // first pass: process the properties
265    for (Property property : properties) {
266      parseChildItem(errors, path, object.getProperties(), element, property);
267    }
268
269    // second pass: check for things not processed (including duplicates)
270    checkNotProcessed(errors, path, element, hasResourceType, object.getProperties());
271    
272    
273    if (object.isExtraComma()) {
274      logError(errors, "2022-11-26", object.getEnd().getLine(), object.getEnd().getCol(), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
275    }
276    return properties;
277  }
278
279  private void checkNotProcessed(List<ValidationMessage> errors, String path, Element element, boolean hasResourceType, List<JsonProperty> children) {
280    if (policy != ValidationPolicy.NONE) {
281      for (JsonProperty e : children) {
282        if (e.getTag() == 0) {
283          StructureDefinition sd = element.getProperty().isLogical() ? getContextUtilities().fetchByJsonName(e.getName()) : null;
284          if (sd != null) {
285            Property property = new Property(context, sd.getSnapshot().getElementFirstRep(), sd, element.getProperty().getUtils(), element.getProperty().getContextUtils());
286            parseChildItem(errors, path, children, element, property);
287          } else if ("fhir_comments".equals(e.getName()) && (VersionUtilities.isR2BVer(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion()))) {
288            if (!e.getValue().isJsonArray()) {
289              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);
290            } else {
291              for (JsonElement c : e.getValue().asJsonArray()) {
292                if (!c.isJsonString()) {
293                  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);
294                } else {
295                  element.getComments().add(c.asString());
296                }
297              }
298            }
299          } else if (hasResourceType && "resourceType".equals(e.getName())) {
300            // nothing
301          } else {
302            JsonProperty p = getFoundJsonPropertyByName(e.getName(), children);
303            if (p != null) {
304              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);            
305            } else {
306              logError(errors, ValidationMessage.NO_RULE_DATE, line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getName()), IssueSeverity.ERROR);
307            }
308          }
309        }
310      }
311    }    
312  }
313
314  private JsonProperty getFoundJsonPropertyByName(String name, List<JsonProperty> children) {
315    int hash = name.hashCode();
316    for (JsonProperty p : children) {
317      if (p.getTag() == 1 && hash == p.getNameHash()) {
318        return p;
319      }
320    }
321    return null;
322  }
323
324  private JsonProperty getJsonPropertyByName(String name, List<JsonProperty> children) {
325    int hash = name.hashCode();
326    for (JsonProperty p : children) {
327      if (p.getTag() == 0 && hash == p.getNameHash()) {
328        return p;
329      }
330    }
331    return null;
332  }
333  
334  private JsonProperty getJsonPropertyByBaseName(String name, List<JsonProperty> children) {
335    for (JsonProperty p : children) {
336      if (p.getTag() == 0 && p.getName().startsWith(name)) {
337        return p;
338      }
339    }
340    return null;
341  }
342  
343  private void processChildren(List<ValidationMessage> errors, String path, JsonObject object) {
344    for (JsonProperty p : object.getProperties()) {
345      if (p.isUnquotedName()) {
346        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);
347      }
348      if (p.isNoComma()) {
349        logError(errors, "2022-11-26", line(p.getValue()), col(p.getValue()), path, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_MISSING), IssueSeverity.ERROR);        
350      }
351    }
352  }
353
354  public void parseChildItem(List<ValidationMessage> errors, String path, List<JsonProperty> children, Element context, Property property) {
355    JsonProperty jp = getJsonPropertyByName(property.getJsonName(), children);
356    if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) {
357      if (property.isJsonPrimitiveChoice()) {
358        if (jp != null) {
359          jp.setTag(1);
360          JsonElement je = jp.getValue();
361          String type = getTypeFromJsonType(je);
362          if (type == null) {
363            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);
364          } else if (property.hasType(type)) {
365            Property np = new Property(property.getContext(), property.getDefinition(), property.getStructure(), property.getUtils(), property.getContextUtils(), type);
366            parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, np, path, property.getName(), false);
367          } else {
368            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);
369          }
370        }
371      } else { 
372        String baseName = property.getJsonName().substring(0, property.getName().length()-3);
373        jp = getJsonPropertyByBaseName(baseName, children);
374        JsonProperty jp1 = getJsonPropertyByBaseName("_"+baseName, children);
375        if (jp != null || jp1 != null) {
376          for (TypeRefComponent type : property.getDefinition().getType()) {
377            String eName = baseName + Utilities.capitalize(type.getWorkingCode());
378            if ((jp != null && jp.getName().equals(eName) || (jp1 != null && jp1.getName().equals("_"+eName)))) {
379              if (!isPrimitive(type.getWorkingCode()) && jp != null) {
380                parseChildComplex(errors, path, jp, context, property, eName, false);
381                break;
382              } else if (isPrimitive(type.getWorkingCode()) && (jp != null || jp1 != null)) {
383                parseChildPrimitive(errors, jp, jp1, context, property, path, eName, false);
384                break;
385              }
386            }
387          }
388        }
389      }
390    } else if (property.isPrimitive(property.getType(null))) {
391      parseChildPrimitive(errors, jp, getJsonPropertyByName("_"+property.getJsonName(), children), context, property, path, property.getJsonName(), property.hasJsonName());
392    } else if (jp != null) {
393      parseChildComplex(errors, path, jp, context, property, property.getJsonName(), property.hasJsonName());
394    }
395  }
396
397
398  private String getTypeFromJsonType(JsonElement je) {
399    if (je.isJsonPrimitive()) {
400      JsonPrimitive p = je.asJsonPrimitive();
401      if (p.isJsonString()) {
402        return "string";
403      } else if (p.isJsonBoolean()) {
404        return "boolean";
405      } else {
406        String s = p.asString();
407        if (Utilities.isInteger(s)) {
408          return "integer";
409        } else {
410          return "decimal";
411        }
412      }
413    } else {
414      return null;
415    }
416  }
417
418  private void parseChildComplex(List<ValidationMessage> errors, String path, JsonProperty p, Element element, Property property, String name, boolean isJsonName) throws FHIRException {
419    String npath = path+"."+property.getName();
420    String fpath = element.getPath()+"."+property.getName();
421    if (p != null) { p.setTag(1); }
422    JsonElement e = p == null ? null : p.getValue();
423    if (property.isList() && !property.isJsonKeyArray() && (e instanceof JsonArray)) {
424      JsonArray arr = (JsonArray) e;
425      if (arr.isExtraComma()) {
426        logError(errors, "2022-11-26", arr.getEnd().getLine(), arr.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
427      }
428      if (arr.size() == 0) {
429        if (property.canBeEmpty()) {
430          // nothing
431        } else {
432          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR);
433        }
434      }
435      int c = 0;
436      List<Property> properties = null;
437      for (JsonElement am : arr) {
438        properties = parseChildComplexInstance(errors, npath+"["+c+"]", fpath+"["+c+"]", element, property, name, am, c == 0 ? arr : null, path, properties);
439        c++;
440      }
441    } else if (property.isJsonKeyArray()) {
442      String code = property.getJsonKeyProperty();
443      List<Property> properties = property.getChildProperties(element.getName(), null);
444      if (properties.size() != 2) {
445        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);               
446      } else {
447        Property propK = properties.get(0);
448        Property propV = properties.get(1);
449        if (!propK.getName().equals(code)) {
450          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);                       
451        } else if (!propK.isPrimitive())  {
452          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);                       
453        } else if (propV.isList())  {
454          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);                       
455        } else if (propV.isChoice() && propV.getName().endsWith("[x]"))  {
456          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);                       
457        } else if (!(e instanceof JsonObject)) {
458          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);                       
459        } else {
460          JsonObject o = (JsonObject) e;
461          if (o.isExtraComma()) {
462            logError(errors, "2022-11-26", o.getEnd().getLine(), o.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
463          }
464
465          int i = 0;
466          Set<String> names = new HashSet<>();
467          for (JsonProperty pv : o.getProperties()) {
468            if (names.contains(pv.getName())) {
469              logError(errors, "2022-11-26", line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.DUPLICATE_JSON_PROPERTY_KEY, pv.getName()), IssueSeverity.ERROR);                                     
470            } else {
471              names.add(pv.getName());
472              pv.setTag(1);
473            }
474            // create an array entry
475            String npathArr = path+"."+property.getName()+"["+i+"]";
476            String fpathArr = element.getPath()+"."+property.getName()+"["+i+"]";
477            
478            Element n = new Element(name, property).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON);
479            n.setPath(fpath);
480            element.getChildren().add(n);
481            // handle the key
482            String fpathKey = fpathArr+"."+propK.getName();
483            Element nKey = new Element(code, propK).markLocation(line(pv.getValue()), col(pv.getValue())).setFormat(FhirFormat.JSON);
484            checkComments(errors, pv.getValue(), n, fpathArr);
485            nKey.setPath(fpathKey);
486            n.getChildren().add(nKey);
487            nKey.setValue(pv.getName());
488            
489            boolean ok = true;
490            Property pvl = propV;
491            if (propV.isJsonPrimitiveChoice()) {
492              ok = false;
493              String type = getTypeFromJsonType(pv.getValue());
494              if (type == null) {
495                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);
496              } else if (propV.hasType(type)) {
497                pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), type);
498                ok = true;
499              } else if (propV.getDefinition().getType().size() == 1 && propV.typeIsConsistent(type)) {
500                pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), propV.getType());
501                ok = true;
502              } else {
503                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);
504              }
505            }
506            if (ok) {
507              // handle the value
508              String npathV = npathArr+"."+pvl.getName();
509              String fpathV = fpathArr+"."+pvl.getName();
510              if (propV.isPrimitive(pvl.getType(null))) {
511                parseChildPrimitiveInstance(errors, n, pvl, pvl.getName(), false, npathV, fpathV, pv.getValue(), null);
512              } else if (pv.getValue() instanceof JsonObject || pv.getValue() instanceof JsonNull) {
513                parseChildComplexInstance(errors, npathV, fpathV, n, pvl, pvl.getName(), pv.getValue(), null, null, null);
514              } else {
515                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);                       
516              }
517            }
518            i++;
519          }
520        }
521      }
522    } else {
523      if (property.isJsonList()) {
524        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);
525      }
526      parseChildComplexInstance(errors, npath, fpath, element, property, name, e, null, null, null);
527    }
528  }
529
530  private Object propNames(List<Property> properties) {
531    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
532    for (Property p: properties) {
533      b.append(p.getName());
534    }
535    return b.toString();
536  }
537
538  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 {
539    if (property.hasTypeSpecifier()) {
540      FHIRPathEngine fpe = new FHIRPathEngine(context);
541      String type = null;
542      String cond = null;
543      for (StringPair sp : property.getTypeSpecifiers()) {
544        if (fpe.evaluateToBoolean(null, baseElement, baseElement, element, fpe.parse(sp.getName()))) {
545          type = sp.getValue();
546          cond = sp.getName();
547          break;
548        }
549      }
550      if (type != null) {
551        StructureDefinition sd = context.fetchResource(StructureDefinition.class, type);
552        if (sd == null) {
553          logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ILLEGAL_TYPE, type, cond), IssueSeverity.ERROR);
554        } else {
555          if (sd.getAbstract()) {
556            logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.TYPE_SPECIFIER_ABSTRACT_TYPE, type, cond), IssueSeverity.ERROR);
557          }
558          property = property.cloneToType(sd);
559        }
560      } else {
561        StructureDefinition sd = context.fetchTypeDefinition(property.getType());
562        if (sd == null) {
563          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);
564        } else if (sd.getAbstract()) {
565          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);
566        }        
567      }
568    }
569    if (e instanceof JsonObject) {
570      JsonObject child = (JsonObject) e;
571      Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON);
572      n.setPath(fpath);
573      checkComments(errors, commentContext, n, commentPath);        
574      checkObject(errors, child, n, npath);
575      element.getChildren().add(n);
576      if (property.isResource()) {
577        parseResource(errors, npath, child, n, property);
578      } else {
579        return parseChildren(errors, npath, child, n, false, properties);
580      }
581    } else if (property.isNullable() && e instanceof JsonNull) {
582      // we create an element marked as a null element so we know something was present
583      JsonNull child = (JsonNull) e;
584      Element n = new Element(name, property).markLocation(line(child), col(child)).setFormat(FhirFormat.JSON);
585      checkComments(errors, commentContext, n, commentPath);        
586      checkComments(errors, child, n, fpath);
587      n.setPath(fpath);
588      element.getChildren().add(n);
589      n.setNull(true);
590      // nothing to do, it's ok, but we treat it like it doesn't exist
591    } else {
592      String msg = context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE__NOT_, (property.isList() ? "an Array" : "an Object"), describe(e), name, npath);
593      logError(errors, ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, msg, IssueSeverity.ERROR);
594    }
595    return null;
596  }
597
598  private String describe(JsonElement e) {
599    if (e instanceof JsonArray) {
600      return "an Array";
601    }
602    if (e instanceof JsonObject) {
603      return "an Object";
604    }
605    if (e instanceof JsonNull) {
606      return "a Null";
607    }
608    if (e instanceof JsonPrimitive) {
609      return "a Primitive property";
610    }
611    return null;
612  }
613
614  private String describeType(JsonElement e) {
615    return e.type().toName();
616  }
617
618//  JsonProperty main = children.containsKey(name) ? children.get(name) : null;
619//  JsonProperty fork = children.containsKey("_"+name) ? children.get("_"+name) : null;
620  private void parseChildPrimitive(List<ValidationMessage> errors, JsonProperty main, JsonProperty fork, Element element, Property property, String path, String name, boolean isJsonName) throws FHIRException {
621    String npath = path+"."+property.getName();
622    String fpath = element.getPath()+"."+property.getName();
623    if (main != null) { main.setTag(1); }
624    if (fork != null) { fork.setTag(1); }
625    
626    if (main != null && main.getValue().isJsonString() && main.isUnquotedValue()) {
627      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);
628    }
629    if (main != null || fork != null) {
630      if (property.isJsonList()) {
631        boolean ok = true;
632        if (!(main == null || main.getValue() instanceof JsonArray)) {
633          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);
634          ok = false;
635        }
636        if (!(fork == null || fork.getValue() instanceof JsonArray)) {
637          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);
638          ok = false;
639        }
640        if (ok) {
641          JsonArray arr1 = (JsonArray) (main == null ? null : main.getValue());
642          JsonArray arr2 = (JsonArray) (fork == null ? null : fork.getValue());
643          if (arr1 != null && arr1.isExtraComma()) {
644            logError(errors, "2022-11-26", arr1.getEnd().getLine(), arr1.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
645          }
646          if (arr2 != null && arr2.isExtraComma()) {
647            logError(errors, "2022-11-26", arr2.getEnd().getLine(), arr2.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Array"), IssueSeverity.ERROR);
648          }
649
650          for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) {
651            JsonElement m = arrI(arr1, i);
652            JsonElement f = arrI(arr2, i);
653            if (m != null && m.isJsonString() && arr1.isUnquoted(i)) {
654              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);
655            }
656            parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, m, f);
657          }
658        }
659      } else {
660        parseChildPrimitiveInstance(errors, element, property, name, isJsonName, npath, fpath, main == null ? null : main.getValue(), fork == null ? null : fork.getValue());
661      }
662    }
663  }
664
665  private JsonElement arrI(JsonArray arr, int i) {
666    return arr == null || i >= arr.size() || arr.get(i) instanceof JsonNull ? null : arr.get(i);
667  }
668
669  private int arrC(JsonArray arr) {
670    return arr == null ? 0 : arr.size();
671  }
672
673  private void parseChildPrimitiveInstance(List<ValidationMessage> errors, Element element, Property property, String name, boolean isJsonName, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException {
674    if (main != null && !(main.isJsonBoolean() || main.isJsonNumber() || main.isJsonString())) {
675      logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(
676          I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR);
677    } else if (fork != null && !(fork instanceof JsonObject)) {
678      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);
679    } else {
680      Element n = new Element(isJsonName ? property.getName() : name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork)).setFormat(FhirFormat.JSON);
681      if (main != null) {
682        checkComments(errors, main, n, npath);
683      }
684      if (fork != null) {
685        checkComments(errors, fork, n, npath);
686      }
687      n.setPath(fpath);
688      element.getChildren().add(n);
689      if (main != null) {
690        JsonPrimitive p = (JsonPrimitive) main;
691        n.setValue(property.hasImpliedPrefix() ? property.getImpliedPrefix()+p.asString() : p.asString());
692        if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) {
693          try {
694            XhtmlParser xhtml = new XhtmlParser();
695            n.setXhtml(xhtml.setXmlMode(true).parse(n.getValue(), null).getDocumentElement());
696            if (policy == ValidationPolicy.EVERYTHING) {
697              for (StringPair s : xhtml.getValidationIssues()) {
698                logError(errors, "2022-11-17", line(main), col(main), npath, IssueType.INVALID, context.formatMessage(s.getName(), s.getValue()), IssueSeverity.ERROR);                
699              }
700            }
701          } catch (Exception e) {
702            logError(errors, ValidationMessage.NO_RULE_DATE, line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_XHTML_, e.getMessage()), IssueSeverity.ERROR);
703          }
704        }
705        if (policy == ValidationPolicy.EVERYTHING) {
706          // now we cross-check the primitive format against the stated type
707          if (Utilities.existsInList(n.getType(), "boolean")) {
708            if (!p.isJsonBoolean()) {
709              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);
710            }
711          } else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) {
712            if (!p.isJsonNumber())
713              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);
714          } else if (!p.isJsonString()) {
715            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);
716          }
717        }
718      }
719      if (fork != null) {
720        JsonObject child = (JsonObject) fork;
721        checkObject(errors, child, n, npath);
722        parseChildren(errors, npath, child, n, false, null);
723      }
724    }
725  }
726
727
728  private void parseResource(List<ValidationMessage> errors, String npath, JsonObject res, Element parent, Property elementProperty) throws FHIRException {
729    JsonElement rt = res.get("resourceType");
730    if (rt == null) {
731      logError(errors, ValidationMessage.NO_RULE_DATE, line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
732    } else if (!rt.isJsonString()) {
733      logError(errors, "2022-11-26", line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.RESOURCETYPE_PROPERTY_WRONG_TYPE, rt.type().toName()), IssueSeverity.FATAL);
734    } else {
735      String name = rt.asString();
736      StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null));
737      if (sd == null) {
738        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);                            
739      } else {
740        parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.getProfileUtilities(), this.getContextUtilities()), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
741        parent.setType(name);
742        parseChildren(errors, npath, res, parent, true, null);
743      }
744    }
745    if (res.isExtraComma()) {
746      logError(errors, "2022-11-26", res.getEnd().getLine(), res.getEnd().getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.JSON_COMMA_EXTRA, "Object"), IssueSeverity.ERROR);
747    }
748
749  }
750
751  private int line(JsonElement e) {
752    return e.getStart().getLine();
753  }
754
755  private int col(JsonElement e) {
756    return e.getEnd().getCol();
757  }
758
759
760  protected void prop(String name, String value, String link) throws IOException {
761    json.link(link);
762    if (name != null)
763      json.name(name);
764    json.value(value);
765  }
766
767  protected void open(String name, String link) throws IOException {
768    json.link(link);
769    if (name != null)
770      json.name(name);
771    json.beginObject();
772  }
773
774  protected void close() throws IOException {
775    json.endObject();
776  }
777
778  protected void openArray(String name, String link) throws IOException {
779    json.link(link);
780    if (name != null)
781      json.name(name);
782    json.beginArray();
783  }
784
785  protected void closeArray() throws IOException {
786    json.endArray();
787  }
788
789
790  @Override
791  public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException {
792    if (e.getPath() == null) {
793      e.populatePaths(null);
794    }
795
796    markedXhtml = false;
797    OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
798    if (style == OutputStyle.CANONICAL) {
799      json = new JsonCreatorCanonical(osw);
800    } else if (style == OutputStyle.PRETTY) {
801      json = new JsonCreatorDirect(osw, true, allowComments);
802    } else {
803      json = new JsonCreatorDirect(osw, false, allowComments);
804    }
805    checkComposeComments(e);
806    json.beginObject();
807    if (!isSuppressResourceType(e.getProperty())) {
808      prop("resourceType", e.getType(), null);
809    }
810    Set<String> done = new HashSet<String>();
811    for (Element child : e.getChildren()) {
812      compose(e.getName(), e, done, child);
813    }
814    json.endObject();
815    json.finish();
816    osw.flush();
817  }
818
819  private boolean isSuppressResourceType(Property property) {
820    StructureDefinition sd = property.getStructure();
821    if (sd != null && sd.hasExtension(ToolingExtensions.EXT_SUPPRESS_RESOURCE_TYPE)) {
822      return ToolingExtensions.readBoolExtension(sd, ToolingExtensions.EXT_SUPPRESS_RESOURCE_TYPE);
823    } else {
824      return false;
825    }
826  }
827
828  private void checkComposeComments(Element e) {
829    for (String s : e.getComments()) {
830      json.comment(s);
831    }
832  }
833
834  public void compose(Element e, JsonCreator json) throws Exception {
835    if (e.getPath() == null) {
836      e.populatePaths(null);
837    }
838    
839    this.json = json;
840    checkComposeComments(e);
841    json.beginObject();
842
843    if (!isSuppressResourceType(e.getProperty())) {
844      prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty()));
845    }
846    Set<String> done = new HashSet<String>();
847    for (Element child : e.getChildren()) {
848      compose(e.getName(), e, done, child);
849    }
850    json.endObject();
851    json.finish();
852  }
853
854  private void compose(String path, Element e, Set<String> done, Element child) throws IOException {
855    checkComposeComments(child);
856    if (wantCompose(path, child)) {
857      boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList();
858      if (!isList) {// for specials, ignore the cardinality of the stated type
859        if (child.isElided() && isElideElements() && json.canElide())
860          json.elide();
861        else
862          compose(child.getName(), path, child);
863      } else if (!done.contains(child.getName())) {
864        done.add(child.getName());
865        List<Element> list = e.getChildrenByName(child.getName());
866        boolean skipList = false;
867        if (json.canElide() && isElideElements()) {
868          boolean foundNonElide = false;
869          for (Element listElement: list) {
870            if (!listElement.isElided()) {
871              foundNonElide = true;
872              break;
873            }
874          }
875          if (!foundNonElide) {
876            json.elide();
877            skipList = true;
878          }
879        }
880        if (!skipList) {
881          if (child.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY))
882            composeKeyList(path, list);
883          else 
884            composeList(list.get(0).getName(), path, list);
885        }
886      }
887    }
888  }
889
890  
891  private void composeKeyList(String path, List<Element> list) throws IOException {
892    String keyName = list.get(0).getProperty().getDefinition().getExtensionString(ToolingExtensions.EXT_JSON_PROP_KEY);
893    json.name(list.get(0).getName());
894    json.beginObject();
895    for (Element e: list) {
896      Element key = null;
897      Element value = null;
898      for (Element child: e.getChildren()) {
899        if (child.getName().equals(keyName))
900          key = child;
901        else
902          value = child;
903      }
904      if (value.isPrimitive())
905        primitiveValue(key.getValue(), value);
906      else {
907        json.name(key.getValue());
908        checkComposeComments(e);
909        json.beginObject();
910        Set<String> done = new HashSet<String>();
911        for (Element child : value.getChildren()) {
912          compose(value.getName(), value, done, child);
913        }
914        json.endObject();
915        compose(value.getName(), path + "." + key.getValue(), value);
916      }
917    }
918    json.endObject();
919  }
920
921  private void composeList(String name, String path, List<Element> list) throws IOException {
922    // there will be at least one element
923    boolean complex = true;
924    if (list.get(0).isPrimitive()) {
925      boolean prim = false;
926      complex = false;
927      for (Element item : list) {
928        if (item.hasValue())
929          prim = true;
930        if (item.hasChildren())
931          complex = true;
932      }
933      if (prim) {
934        openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
935        for (Element item : list) {
936          if (item.isElided() && json.canElide())
937            json.elide();
938          else if (item.hasValue()) {
939            if (linkResolver != null && item.getProperty().isReference()) {
940              String ref = linkResolver.resolveReference(getReferenceForElement(item));
941              if (ref != null) {
942                json.externalLink(ref);
943              }
944            }
945            primitiveValue(null, item);
946          } else
947            json.nullValue();
948        }
949        closeArray();
950      }
951      name = "_"+name;
952    }
953    if (complex) {
954      openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
955      for (Element item : list) {
956        if (item.isElided() && json.canElide())
957          json.elide();
958        else if (item.hasChildren()) {
959          open(null,null);
960          if (item.getProperty().isResource() && !isSuppressResourceType(item.getProperty())) {
961            prop("resourceType", item.getType(), linkResolver == null ? null : linkResolver.resolveType(item.getType()));
962          }
963          if (linkResolver != null && item.getProperty().isReference()) {
964            String ref = linkResolver.resolveReference(getReferenceForElement(item));
965            if (ref != null) {
966              json.externalLink(ref);
967            }
968          }
969          Set<String> done = new HashSet<String>();
970          for (Element child : item.getChildren()) {
971            compose(path+"."+name+"[]", item, done, child);
972          }
973          close();
974        } else {
975          json.nullValue();
976        }
977      }
978      closeArray();
979    }
980  }
981
982  private void primitiveValue(String name, Element item) throws IOException {
983    if (name != null) {
984      if (linkResolver != null)
985        json.link(linkResolver.resolveProperty(item.getProperty()));
986      json.name(name);
987    }
988    String type = item.getType();
989    if (Utilities.existsInList(type, "boolean")) {
990      json.value(item.getValue().trim().equals("true") ? new Boolean(true) : new Boolean(false));
991    } else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt")) {
992      json.value(new Integer(item.getValue()));
993    } else if (Utilities.existsInList(type, "decimal")) {
994      try {
995        json.value(new BigDecimal(item.getValue()));
996      } catch (Exception e) {
997        throw new NumberFormatException(context.formatMessage(I18nConstants.ERROR_WRITING_NUMBER__TO_JSON, item.getValue()));
998      }
999    } else {
1000      json.value(item.getValue());
1001    }
1002  }
1003
1004  private void compose(String name, String path, Element element) throws IOException {
1005    if (element.isPrimitive() || isPrimitive(element.getType())) {
1006      if (element.hasValue())
1007        primitiveValue(name, element);
1008      name = "_"+name;
1009      if (!markedXhtml && element.getType().equals("xhtml"))
1010        json.anchor("end-xhtml");
1011        markedXhtml = true;
1012    }
1013    if (element.hasChildren()) {
1014      open(name, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
1015      if (element.getProperty().isResource() && !isSuppressResourceType(element.getProperty())) {
1016        prop("resourceType", element.getType(), linkResolver == null ? null : linkResolver.resolveType(element.getType()));
1017      }
1018      if (linkResolver != null && element.getProperty().isReference()) {
1019        String ref = linkResolver.resolveReference(getReferenceForElement(element));
1020        if (ref != null) {
1021          json.externalLink(ref);
1022        }
1023      }
1024
1025      if ("named-elements".equals(element.getProperty().getDefinition().getExtensionString(ToolingExtensions.EXT_EXTENSION_STYLE))) {
1026        composeNamedChildren(path + "." + element.getJsonName(), element);        
1027      } else {
1028        Set<String> done = new HashSet<String>();
1029        for (Element child : element.getChildren()) {
1030          compose(path + "." + element.getJsonName(), element, done, child);
1031        }
1032      }
1033      close();
1034    }
1035  }
1036
1037  private void composeNamedChildren(String path, Element element) throws IOException {
1038    Map<String, StructureDefinition> names = new HashMap<>();
1039    for (Element child : element.getChildren()) {
1040      String name = child.getJsonName();
1041      StructureDefinition sd = child.getProperty().getStructure();
1042      if (!names.containsKey(name)) {
1043        names.put(name, sd);        
1044      } else if (names.get(name) != sd) {
1045        throw new FHIRException("Error: conflicting definitions for "+name+" at "+path+": "+sd.getVersionedUrl()+" / "+names.get(name).getVersionedUrl());
1046      }
1047    }
1048    for (String name : Utilities.sorted(names.keySet())) {
1049      StructureDefinition sd = names.get(name);
1050      ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
1051      boolean list = !"1".equals(ed.getMax());
1052      List<Element> children = new ArrayList<>();
1053      for (Element child : element.getChildren()) {
1054        if (name.equals(child.getJsonName())) {
1055          children.add(child);
1056        }
1057      }
1058      if (list) {        
1059        composeList(name, path+"."+name, children);
1060      } else if (children.size() > 1) {
1061        throw new FHIRException("Error: definitions for "+name+" ("+sd.getVersionedUrl()+") says it cannot repeat, but "+children.size()+" items found");        
1062      } else {
1063        compose(name, path+"."+name, children.get(0));
1064      }
1065    }
1066  }
1067
1068  public boolean isAllowComments() {
1069    return allowComments;
1070  }
1071
1072  public JsonParser setAllowComments(boolean allowComments) {
1073    this.allowComments = allowComments;
1074    return this;
1075  }
1076
1077  public boolean isElideElements() {
1078    return elideElements;
1079  }
1080
1081  public JsonParser setElideElements(boolean elideElements) {
1082    this.elideElements = elideElements;
1083    return this;
1084  }
1085/*
1086  public boolean isSuppressResourceType() {
1087    return suppressResourceType;
1088  }
1089
1090  public JsonParser setSuppressResourceType(boolean suppressResourceType) {
1091    this.suppressResourceType = suppressResourceType;
1092    return this;
1093  }
1094*/
1095
1096  public ILogicalModelResolver getLogicalModelResolver() {
1097    return logicalModelResolver;
1098  }
1099
1100  public JsonParser setLogicalModelResolver(ILogicalModelResolver logicalModelResolver) {
1101    this.logicalModelResolver = logicalModelResolver;
1102    return this;
1103  }
1104
1105}