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