001package ca.uhn.fhir.parser;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 * http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
025import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
026import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
027import ca.uhn.fhir.context.ConfigurationException;
028import ca.uhn.fhir.context.FhirContext;
029import ca.uhn.fhir.context.FhirVersionEnum;
030import ca.uhn.fhir.context.RuntimeChildContainedResources;
031import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
032import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
033import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
034import ca.uhn.fhir.context.RuntimeResourceDefinition;
035import ca.uhn.fhir.i18n.Msg;
036import ca.uhn.fhir.model.api.ExtensionDt;
037import ca.uhn.fhir.model.api.IPrimitiveDatatype;
038import ca.uhn.fhir.model.api.IResource;
039import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
040import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
041import ca.uhn.fhir.model.api.Tag;
042import ca.uhn.fhir.model.api.TagList;
043import ca.uhn.fhir.model.api.annotation.Child;
044import ca.uhn.fhir.model.base.composite.BaseCodingDt;
045import ca.uhn.fhir.model.base.composite.BaseContainedDt;
046import ca.uhn.fhir.model.primitive.IdDt;
047import ca.uhn.fhir.model.primitive.InstantDt;
048import ca.uhn.fhir.narrative.INarrativeGenerator;
049import ca.uhn.fhir.parser.json.BaseJsonLikeArray;
050import ca.uhn.fhir.parser.json.BaseJsonLikeObject;
051import ca.uhn.fhir.parser.json.BaseJsonLikeValue;
052import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ScalarType;
053import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ValueType;
054import ca.uhn.fhir.parser.json.BaseJsonLikeWriter;
055import ca.uhn.fhir.parser.json.JsonLikeStructure;
056import ca.uhn.fhir.parser.json.jackson.JacksonStructure;
057import ca.uhn.fhir.rest.api.EncodingEnum;
058import ca.uhn.fhir.util.ElementUtil;
059import org.apache.commons.lang3.StringUtils;
060import org.apache.commons.lang3.Validate;
061import org.apache.commons.text.WordUtils;
062import org.hl7.fhir.instance.model.api.IBase;
063import org.hl7.fhir.instance.model.api.IBaseBooleanDatatype;
064import org.hl7.fhir.instance.model.api.IBaseDecimalDatatype;
065import org.hl7.fhir.instance.model.api.IBaseExtension;
066import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
067import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
068import org.hl7.fhir.instance.model.api.IBaseIntegerDatatype;
069import org.hl7.fhir.instance.model.api.IBaseResource;
070import org.hl7.fhir.instance.model.api.IDomainResource;
071import org.hl7.fhir.instance.model.api.IIdType;
072import org.hl7.fhir.instance.model.api.INarrative;
073import org.hl7.fhir.instance.model.api.IPrimitiveType;
074
075import java.io.IOException;
076import java.io.Reader;
077import java.io.Writer;
078import java.math.BigDecimal;
079import java.util.ArrayList;
080import java.util.Collections;
081import java.util.Iterator;
082import java.util.List;
083import java.util.Map;
084
085import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE;
086import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE;
087import static org.apache.commons.lang3.StringUtils.defaultString;
088import static org.apache.commons.lang3.StringUtils.isBlank;
089import static org.apache.commons.lang3.StringUtils.isNotBlank;
090
091/**
092 * This class is the FHIR JSON parser/encoder. Users should not interact with this class directly, but should use
093 * {@link FhirContext#newJsonParser()} to get an instance.
094 */
095public class JsonParser extends BaseParser implements IJsonLikeParser {
096
097        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParser.HeldExtension.class);
098
099        private boolean myPrettyPrint;
100
101        private Boolean myIsSupportsFhirComment;
102
103        /**
104         * Do not use this constructor, the recommended way to obtain a new instance of the JSON parser is to invoke
105         * {@link FhirContext#newJsonParser()}.
106         *
107         * @param theParserErrorHandler
108         */
109        public JsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
110                super(theContext, theParserErrorHandler);
111        }
112
113        private boolean addToHeldComments(int valueIdx, List<String> theCommentsToAdd, ArrayList<ArrayList<String>> theListToAddTo) {
114                if (theCommentsToAdd.size() > 0) {
115                        theListToAddTo.ensureCapacity(valueIdx);
116                        while (theListToAddTo.size() <= valueIdx) {
117                                theListToAddTo.add(null);
118                        }
119                        if (theListToAddTo.get(valueIdx) == null) {
120                                theListToAddTo.set(valueIdx, new ArrayList<>());
121                        }
122                        theListToAddTo.get(valueIdx).addAll(theCommentsToAdd);
123                        return true;
124                }
125                return false;
126        }
127
128        private boolean addToHeldExtensions(int valueIdx, List<? extends IBaseExtension<?, ?>> ext, ArrayList<ArrayList<HeldExtension>> list, boolean theIsModifier, CompositeChildElement theChildElem,
129                                                                                                        CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theContainingElement) {
130                boolean retVal = false;
131                if (ext.size() > 0) {
132                        Boolean encodeExtension = null;
133                        for (IBaseExtension<?, ?> next : ext) {
134
135                                if (next.isEmpty()) {
136                                        continue;
137                                }
138
139                                // Make sure we respect _summary and _elements
140                                if (encodeExtension == null) {
141                                        encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, theContainingElement);
142                                }
143
144                                if (encodeExtension) {
145                                        HeldExtension extension = new HeldExtension(next, theIsModifier, theChildElem, theParent);
146                                        list.ensureCapacity(valueIdx);
147                                        while (list.size() <= valueIdx) {
148                                                list.add(null);
149                                        }
150                                        ArrayList<HeldExtension> extensionList = list.get(valueIdx);
151                                        if (extensionList == null) {
152                                                extensionList = new ArrayList<>();
153                                                list.set(valueIdx, extensionList);
154                                        }
155                                        extensionList.add(extension);
156                                        retVal = true;
157                                }
158                        }
159                }
160                return retVal;
161        }
162
163        private void addToHeldIds(int theValueIdx, ArrayList<String> theListToAddTo, String theId) {
164                theListToAddTo.ensureCapacity(theValueIdx);
165                while (theListToAddTo.size() <= theValueIdx) {
166                        theListToAddTo.add(null);
167                }
168                if (theListToAddTo.get(theValueIdx) == null) {
169                        theListToAddTo.set(theValueIdx, theId);
170                }
171        }
172
173        // private void assertObjectOfType(JsonLikeValue theResourceTypeObj, Object theValueType, String thePosition) {
174        // if (theResourceTypeObj == null) {
175        // throw new DataFormatException(Msg.code(1836) + "Invalid JSON content detected, missing required element: '" + thePosition + "'");
176        // }
177        //
178        // if (theResourceTypeObj.getValueType() != theValueType) {
179        // throw new DataFormatException(Msg.code(1837) + "Invalid content of element " + thePosition + ", expected " + theValueType);
180        // }
181        // }
182
183        private void beginArray(BaseJsonLikeWriter theEventWriter, String arrayName) throws IOException {
184                theEventWriter.beginArray(arrayName);
185        }
186
187        private void beginObject(BaseJsonLikeWriter theEventWriter, String arrayName) throws IOException {
188                theEventWriter.beginObject(arrayName);
189        }
190
191        private BaseJsonLikeWriter createJsonWriter(Writer theWriter) throws IOException {
192                JsonLikeStructure jsonStructure = new JacksonStructure();
193                return jsonStructure.getJsonLikeWriter(theWriter);
194        }
195
196        public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, BaseJsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
197                if (myPrettyPrint) {
198                        theEventWriter.setPrettyPrint(myPrettyPrint);
199                }
200                theEventWriter.init();
201
202                RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
203                encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, theEncodeContext);
204                theEventWriter.flush();
205        }
206
207        @Override
208        protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
209                BaseJsonLikeWriter eventWriter = createJsonWriter(theWriter);
210                doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext);
211                eventWriter.close();
212        }
213
214        @Override
215        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
216                JsonLikeStructure jsonStructure = new JacksonStructure();
217                jsonStructure.load(theReader);
218
219                T retVal = doParseResource(theResourceType, jsonStructure);
220
221                return retVal;
222        }
223
224        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, JsonLikeStructure theJsonStructure) {
225                BaseJsonLikeObject object = theJsonStructure.getRootObject();
226
227                BaseJsonLikeValue resourceTypeObj = object.get("resourceType");
228                if (resourceTypeObj == null || !resourceTypeObj.isString() || isBlank(resourceTypeObj.getAsString())) {
229                        throw new DataFormatException(Msg.code(1838) + "Invalid JSON content detected, missing required element: 'resourceType'");
230                }
231
232                String resourceType = resourceTypeObj.getAsString();
233
234                ParserState<? extends IBaseResource> state = ParserState.getPreResourceInstance(this, theResourceType, getContext(), true, getErrorHandler());
235                state.enteringNewElement(null, resourceType);
236
237                parseChildren(object, state);
238
239                state.endingElement();
240                state.endingElement();
241
242                @SuppressWarnings("unchecked")
243                T retVal = (T) state.getObject();
244
245                return retVal;
246        }
247
248        private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, BaseJsonLikeWriter theEventWriter, IBase theNextValue,
249                                                                                                                                 BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem,
250                                                                                                                                 boolean theForceEmpty, EncodeContext theEncodeContext) throws IOException {
251
252                switch (theChildDef.getChildType()) {
253                        case ID_DATATYPE: {
254                                IIdType value = (IIdType) theNextValue;
255                                String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
256                                if (isBlank(encodedValue)) {
257                                        break;
258                                }
259                                if (theChildName != null) {
260                                        write(theEventWriter, theChildName, encodedValue);
261                                } else {
262                                        theEventWriter.write(encodedValue);
263                                }
264                                break;
265                        }
266                        case PRIMITIVE_DATATYPE: {
267                                final IPrimitiveType<?> value = (IPrimitiveType<?>) theNextValue;
268                                final String valueStr = value.getValueAsString();
269                                if (isBlank(valueStr)) {
270                                        if (theForceEmpty) {
271                                                theEventWriter.writeNull();
272                                        }
273                                        break;
274                                }
275
276                                // check for the common case first - String value types
277                                Object valueObj = value.getValue();
278                                if (valueObj instanceof String) {
279                                        if (theChildName != null) {
280                                                theEventWriter.write(theChildName, valueStr);
281                                        } else {
282                                                theEventWriter.write(valueStr);
283                                        }
284                                        break;
285                                } else if (valueObj instanceof Long) {
286                                        if (theChildName != null) {
287                                                theEventWriter.write(theChildName, (long) valueObj);
288                                        } else {
289                                                theEventWriter.write((long) valueObj);
290                                        }
291                                        break;
292                                }
293
294                                if (value instanceof IBaseIntegerDatatype) {
295                                        if (theChildName != null) {
296                                                write(theEventWriter, theChildName, ((IBaseIntegerDatatype) value).getValue());
297                                        } else {
298                                                theEventWriter.write(((IBaseIntegerDatatype) value).getValue());
299                                        }
300                                } else if (value instanceof IBaseDecimalDatatype) {
301                                        BigDecimal decimalValue = ((IBaseDecimalDatatype) value).getValue();
302                                        decimalValue = new BigDecimal(decimalValue.toString()) {
303                                                private static final long serialVersionUID = 1L;
304
305                                                @Override
306                                                public String toString() {
307                                                        return value.getValueAsString();
308                                                }
309                                        };
310                                        if (theChildName != null) {
311                                                write(theEventWriter, theChildName, decimalValue);
312                                        } else {
313                                                theEventWriter.write(decimalValue);
314                                        }
315                                } else if (value instanceof IBaseBooleanDatatype) {
316                                        if (theChildName != null) {
317                                                write(theEventWriter, theChildName, ((IBaseBooleanDatatype) value).getValue());
318                                        } else {
319                                                Boolean booleanValue = ((IBaseBooleanDatatype) value).getValue();
320                                                if (booleanValue != null) {
321                                                        theEventWriter.write(booleanValue.booleanValue());
322                                                }
323                                        }
324                                } else {
325                                        if (theChildName != null) {
326                                                write(theEventWriter, theChildName, valueStr);
327                                        } else {
328                                                theEventWriter.write(valueStr);
329                                        }
330                                }
331                                break;
332                        }
333                        case RESOURCE_BLOCK:
334                        case COMPOSITE_DATATYPE: {
335                                if (theChildName != null) {
336                                        theEventWriter.beginObject(theChildName);
337                                } else {
338                                        theEventWriter.beginObject();
339                                }
340                                encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem, theEncodeContext);
341                                theEventWriter.endObject();
342                                break;
343                        }
344                        case CONTAINED_RESOURCE_LIST:
345                        case CONTAINED_RESOURCES: {
346                                List<IBaseResource> containedResources = getContainedResources().getContainedResources();
347                                if (containedResources.size() > 0) {
348                                        beginArray(theEventWriter, theChildName);
349
350                                        for (IBaseResource next : containedResources) {
351                                                IIdType resourceId = getContainedResources().getResourceId(next);
352                                                String value = resourceId.getValue();
353                                                encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(value), theEncodeContext);
354                                        }
355
356                                        theEventWriter.endArray();
357                                }
358                                break;
359                        }
360                        case PRIMITIVE_XHTML_HL7ORG:
361                        case PRIMITIVE_XHTML: {
362                                if (!isSuppressNarratives()) {
363                                        IPrimitiveType<?> dt = (IPrimitiveType<?>) theNextValue;
364                                        if (theChildName != null) {
365                                                write(theEventWriter, theChildName, dt.getValueAsString());
366                                        } else {
367                                                theEventWriter.write(dt.getValueAsString());
368                                        }
369                                } else {
370                                        if (theChildName != null) {
371                                                // do nothing
372                                        } else {
373                                                theEventWriter.writeNull();
374                                        }
375                                }
376                                break;
377                        }
378                        case RESOURCE:
379                                IBaseResource resource = (IBaseResource) theNextValue;
380                                RuntimeResourceDefinition def = getContext().getResourceDefinition(resource);
381
382                                theEncodeContext.pushPath(def.getName(), true);
383                                encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, theContainedResource, theEncodeContext);
384                                theEncodeContext.popPath();
385
386                                break;
387                        case UNDECL_EXT:
388                        default:
389                                throw new IllegalStateException(Msg.code(1839) + "Should not have this state here: " + theChildDef.getChildType().name());
390                }
391
392        }
393
394        private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, BaseJsonLikeWriter theEventWriter,
395                                                                                                                                                                 boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException {
396
397                {
398                        String elementId = getCompositeElementId(theElement);
399                        if (isNotBlank(elementId)) {
400                                write(theEventWriter, "id", elementId);
401                        }
402                }
403
404                boolean haveWrittenExtensions = false;
405                Iterable<CompositeChildElement> compositeChildElements = super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext);
406                for (CompositeChildElement nextChildElem : compositeChildElements) {
407
408                        BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
409
410                        if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension")
411                                || nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
412                                if (!haveWrittenExtensions) {
413                                        extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, getContext().getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent, theEncodeContext, theContainedResource);
414                                        haveWrittenExtensions = true;
415                                }
416                                continue;
417                        }
418
419                        if (nextChild instanceof RuntimeChildNarrativeDefinition) {
420                                INarrativeGenerator gen = getContext().getNarrativeGenerator();
421                                if (gen != null) {
422                                        INarrative narr;
423                                        if (theResource instanceof IResource) {
424                                                narr = ((IResource) theResource).getText();
425                                        } else if (theResource instanceof IDomainResource) {
426                                                narr = ((IDomainResource) theResource).getText();
427                                        } else {
428                                                narr = null;
429                                        }
430                                        if (narr != null && narr.isEmpty()) {
431                                                gen.populateResourceNarrative(getContext(), theResource);
432                                                if (!narr.isEmpty()) {
433                                                        RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
434                                                        String childName = nextChild.getChildNameByDatatype(child.getDatatype());
435                                                        BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
436                                                        encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, nextChildElem, false, theEncodeContext);
437                                                        continue;
438                                                }
439                                        }
440                                }
441                        } else if (nextChild instanceof RuntimeChildContainedResources) {
442                                String childName = nextChild.getValidChildNames().iterator().next();
443                                BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
444                                encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, nextChildElem, false, theEncodeContext);
445                                continue;
446                        }
447
448                        List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
449                        values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
450
451                        if (values == null || values.isEmpty()) {
452                                continue;
453                        }
454
455                        String currentChildName = null;
456                        boolean inArray = false;
457
458                        ArrayList<ArrayList<HeldExtension>> extensions = new ArrayList<>(0);
459                        ArrayList<ArrayList<HeldExtension>> modifierExtensions = new ArrayList<>(0);
460                        ArrayList<ArrayList<String>> comments = new ArrayList<>(0);
461                        ArrayList<String> ids = new ArrayList<>(0);
462
463                        int valueIdx = 0;
464                        for (IBase nextValue : values) {
465
466                                if (nextValue == null || nextValue.isEmpty()) {
467                                        if (nextValue instanceof BaseContainedDt) {
468                                                if (theContainedResource || getContainedResources().isEmpty()) {
469                                                        continue;
470                                                }
471                                        } else {
472                                                continue;
473                                        }
474                                }
475
476                                BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
477                                if (childNameAndDef == null) {
478                                        continue;
479                                }
480
481                                /*
482                                 * Often the two values below will be the same thing. There are cases though
483                                 * where they will not be. An example would be Observation.value, which is
484                                 * a choice type. If the value contains a Quantity, then:
485                                 * nextChildGenericName = "value"
486                                 * nextChildSpecificName = "valueQuantity"
487                                 */
488                                String nextChildSpecificName = childNameAndDef.getChildName();
489                                String nextChildGenericName = nextChild.getElementName();
490
491                                theEncodeContext.pushPath(nextChildGenericName, false);
492
493                                BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
494                                boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
495
496                                if ((childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && theContainedResource) {
497                                        continue;
498                                }
499
500                                boolean force = false;
501                                if (primitive) {
502                                        if (nextValue instanceof ISupportsUndeclaredExtensions) {
503                                                List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions();
504                                                force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement);
505
506                                                ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions();
507                                                force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement);
508                                        } else {
509                                                if (nextValue instanceof IBaseHasExtensions) {
510                                                        IBaseHasExtensions element = (IBaseHasExtensions) nextValue;
511                                                        List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
512                                                        force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement);
513                                                }
514                                                if (nextValue instanceof IBaseHasModifierExtensions) {
515                                                        IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue;
516                                                        List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
517                                                        force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement);
518                                                }
519                                        }
520                                        if (nextValue.hasFormatComment()) {
521                                                force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPre(), comments);
522                                                force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPost(), comments);
523                                        }
524                                        String elementId = getCompositeElementId(nextValue);
525                                        if (isNotBlank(elementId)) {
526                                                force = true;
527                                                addToHeldIds(valueIdx, ids, elementId);
528                                        }
529                                }
530
531                                if (currentChildName == null || !currentChildName.equals(nextChildSpecificName)) {
532                                        if (inArray) {
533                                                theEventWriter.endArray();
534                                        }
535                                        BaseRuntimeChildDefinition replacedParentDefinition = nextChild.getReplacedParentDefinition();
536                                        if (isMultipleCardinality(nextChild.getMax()) || (replacedParentDefinition != null && isMultipleCardinality(replacedParentDefinition.getMax()))) {
537                                                beginArray(theEventWriter, nextChildSpecificName);
538                                                inArray = true;
539                                                encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
540                                        } else {
541                                                encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, nextChildSpecificName, theContainedResource, nextChildElem, false, theEncodeContext);
542                                        }
543                                        currentChildName = nextChildSpecificName;
544                                } else {
545                                        encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
546                                }
547
548                                valueIdx++;
549                                theEncodeContext.popPath();
550                        }
551
552                        if (inArray) {
553                                theEventWriter.endArray();
554                        }
555
556
557                        if (!extensions.isEmpty() || !modifierExtensions.isEmpty() || !comments.isEmpty()) {
558                                if (inArray) {
559                                        // If this is a repeatable field, the extensions go in an array too
560                                        beginArray(theEventWriter, '_' + currentChildName);
561                                } else {
562                                        beginObject(theEventWriter, '_' + currentChildName);
563                                }
564
565                                for (int i = 0; i < valueIdx; i++) {
566                                        boolean haveContent = false;
567
568                                        List<HeldExtension> heldExts = Collections.emptyList();
569                                        List<HeldExtension> heldModExts = Collections.emptyList();
570                                        if (extensions.size() > i && extensions.get(i) != null && extensions.get(i).isEmpty() == false) {
571                                                haveContent = true;
572                                                heldExts = extensions.get(i);
573                                        }
574
575                                        if (modifierExtensions.size() > i && modifierExtensions.get(i) != null && modifierExtensions.get(i).isEmpty() == false) {
576                                                haveContent = true;
577                                                heldModExts = modifierExtensions.get(i);
578                                        }
579
580                                        ArrayList<String> nextComments;
581                                        if (comments.size() > i) {
582                                                nextComments = comments.get(i);
583                                        } else {
584                                                nextComments = null;
585                                        }
586                                        if (nextComments != null && nextComments.isEmpty() == false) {
587                                                haveContent = true;
588                                        }
589
590                                        String elementId = null;
591                                        if (ids.size() > i) {
592                                                elementId = ids.get(i);
593                                                haveContent |= isNotBlank(elementId);
594                                        }
595
596                                        if (!haveContent) {
597                                                theEventWriter.writeNull();
598                                        } else {
599                                                if (inArray) {
600                                                        theEventWriter.beginObject();
601                                                }
602                                                if (isNotBlank(elementId)) {
603                                                        write(theEventWriter, "id", elementId);
604                                                }
605                                                if (nextComments != null && !nextComments.isEmpty()) {
606                                                        if (isSupportsFhirComment()) {
607                                                                beginArray(theEventWriter, "fhir_comments");
608                                                                for (String next : nextComments) {
609                                                                        theEventWriter.write(next);
610                                                                }
611                                                                theEventWriter.endArray();
612                                                        }
613                                                }
614                                                writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext, theContainedResource);
615                                                if (inArray) {
616                                                        theEventWriter.endObject();
617                                                }
618                                        }
619                                }
620
621                                if (inArray) {
622                                        theEventWriter.endArray();
623                                } else {
624                                        theEventWriter.endObject();
625                                }
626                        }
627                }
628        }
629
630        private boolean isSupportsFhirComment() {
631                if (myIsSupportsFhirComment == null) {
632                        myIsSupportsFhirComment = isFhirVersionLessThanOrEqualTo(FhirVersionEnum.DSTU2_1);
633                }
634                return myIsSupportsFhirComment;
635        }
636
637        private boolean isMultipleCardinality(int maxCardinality) {
638                return maxCardinality > 1 || maxCardinality == Child.MAX_UNLIMITED;
639        }
640
641        private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, BaseJsonLikeWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException, DataFormatException {
642
643                writeCommentsPreAndPost(theNextValue, theEventWriter);
644                encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent, theEncodeContext);
645        }
646
647        @Override
648        public void encodeResourceToJsonLikeWriter(IBaseResource theResource, BaseJsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException {
649                Validate.notNull(theResource, "theResource can not be null");
650                Validate.notNull(theJsonLikeWriter, "theJsonLikeWriter can not be null");
651
652                if (theResource.getStructureFhirVersionEnum() != getContext().getVersion().getVersion()) {
653                        throw new IllegalArgumentException(Msg.code(1840) + "This parser is for FHIR version " + getContext().getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
654                }
655
656                EncodeContext encodeContext = new EncodeContext();
657                String resourceName = getContext().getResourceType(theResource);
658                encodeContext.pushPath(resourceName, true);
659                doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
660        }
661
662        private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, BaseJsonLikeWriter theEventWriter, String theObjectNameOrNull,
663                                                                                                                                 boolean theContainedResource, EncodeContext theEncodeContext) throws IOException {
664                IIdType resourceId = null;
665
666                if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
667                        resourceId = theResource.getIdElement();
668                        if (theResource.getIdElement().getValue().startsWith("urn:")) {
669                                resourceId = null;
670                        }
671                }
672
673                if (!theContainedResource) {
674                        if (!super.shouldEncodeResourceId(theResource, theEncodeContext)) {
675                                resourceId = null;
676                        } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
677                                resourceId = getEncodeForceResourceId();
678                        }
679                }
680
681                encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, resourceId, theEncodeContext);
682        }
683
684        private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, BaseJsonLikeWriter theEventWriter, String theObjectNameOrNull,
685                                                                                                                                 boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws IOException {
686
687                if (!super.shouldEncodeResource(theResDef.getName())) {
688                        return;
689                }
690
691                if (!theContainedResource) {
692                        setContainedResources(getContext().newTerser().containResources(theResource));
693                }
694
695                RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
696
697                if (theObjectNameOrNull == null) {
698                        theEventWriter.beginObject();
699                } else {
700                        beginObject(theEventWriter, theObjectNameOrNull);
701                }
702
703                write(theEventWriter, "resourceType", resDef.getName());
704                if (theResourceId != null && theResourceId.hasIdPart()) {
705                        write(theEventWriter, "id", theResourceId.getIdPart());
706                        final List<HeldExtension> extensions = new ArrayList<>(0);
707                        final List<HeldExtension> modifierExtensions = new ArrayList<>(0);
708                        // Undeclared extensions
709                        extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null, theEncodeContext, theContainedResource);
710                        boolean haveExtension = false;
711                        if (!extensions.isEmpty()) {
712                                haveExtension = true;
713                        }
714
715                        if (theResourceId.hasFormatComment() || haveExtension) {
716                                beginObject(theEventWriter, "_id");
717                                if (theResourceId.hasFormatComment()) {
718                                        writeCommentsPreAndPost(theResourceId, theEventWriter);
719                                }
720                                if (haveExtension) {
721                                        writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource);
722                                }
723                                theEventWriter.endObject();
724                        }
725                }
726
727                if (theResource instanceof IResource) {
728                        IResource resource = (IResource) theResource;
729                        // Object securityLabelRawObj =
730
731                        List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
732                        List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
733                        profiles = super.getProfileTagsForEncoding(resource, profiles);
734
735                        TagList tags = getMetaTagsForEncoding(resource, theEncodeContext);
736                        InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
737                        IdDt resourceId = resource.getId();
738                        String versionIdPart = resourceId.getVersionIdPart();
739                        if (isBlank(versionIdPart)) {
740                                versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
741                        }
742                        List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource);
743
744                        if (super.shouldEncodeResourceMeta(resource) && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) || !extensionMetadataKeys.isEmpty()) {
745                                beginObject(theEventWriter, "meta");
746
747                                if (shouldEncodePath(resource, "meta.versionId")) {
748                                        writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart);
749                                }
750                                if (shouldEncodePath(resource, "meta.lastUpdated")) {
751                                        writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated);
752                                }
753
754                                if (profiles != null && profiles.isEmpty() == false) {
755                                        beginArray(theEventWriter, "profile");
756                                        for (IIdType profile : profiles) {
757                                                if (profile != null && isNotBlank(profile.getValue())) {
758                                                        theEventWriter.write(profile.getValue());
759                                                }
760                                        }
761                                        theEventWriter.endArray();
762                                }
763
764                                if (securityLabels.isEmpty() == false) {
765                                        beginArray(theEventWriter, "security");
766                                        for (BaseCodingDt securityLabel : securityLabels) {
767                                                theEventWriter.beginObject();
768                                                theEncodeContext.pushPath("security", false);
769                                                encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
770                                                theEncodeContext.popPath();
771                                                theEventWriter.endObject();
772                                        }
773                                        theEventWriter.endArray();
774                                }
775
776                                if (tags != null && tags.isEmpty() == false) {
777                                        beginArray(theEventWriter, "tag");
778                                        for (Tag tag : tags) {
779                                                if (tag.isEmpty()) {
780                                                        continue;
781                                                }
782                                                theEventWriter.beginObject();
783                                                writeOptionalTagWithTextNode(theEventWriter, "system", tag.getScheme());
784                                                writeOptionalTagWithTextNode(theEventWriter, "code", tag.getTerm());
785                                                writeOptionalTagWithTextNode(theEventWriter, "display", tag.getLabel());
786                                                theEventWriter.endObject();
787                                        }
788                                        theEventWriter.endArray();
789                                }
790
791                                addExtensionMetadata(theResDef, theResource, theContainedResource, extensionMetadataKeys, resDef, theEventWriter, theEncodeContext);
792
793                                theEventWriter.endObject(); // end meta
794                        }
795                }
796
797                encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
798
799                theEventWriter.endObject();
800        }
801
802
803        private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource,
804                                                                                                 boolean theContainedResource,
805                                                                                                 List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys,
806                                                                                                 RuntimeResourceDefinition resDef,
807                                                                                                 BaseJsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
808                if (extensionMetadataKeys.isEmpty()) {
809                        return;
810                }
811
812                ExtensionDt metaResource = new ExtensionDt();
813                for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) {
814                        metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue());
815                }
816                encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
817        }
818
819        /**
820         * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object
821         * called _name): resource extensions, and extension extensions
822         */
823        private void extractAndWriteExtensionsAsDirectChild(IBase theElement, BaseJsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef,
824                                                                                                                                                 IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException {
825                List<HeldExtension> extensions = new ArrayList<>(0);
826                List<HeldExtension> modifierExtensions = new ArrayList<>(0);
827
828                // Undeclared extensions
829                extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent, theEncodeContext, theContainedResource);
830
831                // Declared extensions
832                if (theElementDef != null) {
833                        extractDeclaredExtensions(theElement, theElementDef, extensions, modifierExtensions, theChildElem);
834                }
835
836                // Write the extensions
837                writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource);
838        }
839
840        private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions,
841                                                                                                                CompositeChildElement theChildElem) {
842                for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) {
843                        for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
844                                if (nextValue != null) {
845                                        if (nextValue.isEmpty()) {
846                                                continue;
847                                        }
848                                        extensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
849                                }
850                        }
851                }
852                for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) {
853                        for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
854                                if (nextValue != null) {
855                                        if (nextValue.isEmpty()) {
856                                                continue;
857                                        }
858                                        modifierExtensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
859                                }
860                        }
861                }
862        }
863
864        private void extractUndeclaredExtensions(IBase theElement, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, CompositeChildElement theChildElem,
865                                                                                                                  CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource) {
866                if (theElement instanceof ISupportsUndeclaredExtensions) {
867                        ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement;
868                        List<ExtensionDt> ext = element.getUndeclaredExtensions();
869                        for (ExtensionDt next : ext) {
870                                if (next == null || next.isEmpty()) {
871                                        continue;
872                                }
873                                extensions.add(new HeldExtension(next, false, theChildElem, theParent));
874                        }
875
876                        ext = element.getUndeclaredModifierExtensions();
877                        for (ExtensionDt next : ext) {
878                                if (next == null || next.isEmpty()) {
879                                        continue;
880                                }
881                                modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent));
882                        }
883                } else {
884                        if (theElement instanceof IBaseHasExtensions) {
885                                IBaseHasExtensions element = (IBaseHasExtensions) theElement;
886                                List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
887                                Boolean encodeExtension = null;
888                                for (IBaseExtension<?, ?> next : ext) {
889                                        if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
890                                                continue;
891                                        }
892
893                                        // Make sure we respect _elements and _summary
894                                        if (encodeExtension == null) {
895                                                encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, element);
896                                        }
897                                        if (encodeExtension) {
898                                                HeldExtension extension = new HeldExtension(next, false, theChildElem, theParent);
899                                                extensions.add(extension);
900                                        }
901
902                                }
903                        }
904                        if (theElement instanceof IBaseHasModifierExtensions) {
905                                IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) theElement;
906                                List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
907                                for (IBaseExtension<?, ?> next : ext) {
908                                        if (next == null || next.isEmpty()) {
909                                                continue;
910                                        }
911
912                                        HeldExtension extension = new HeldExtension(next, true, theChildElem, theParent);
913                                        modifierExtensions.add(extension);
914                                }
915                        }
916                }
917        }
918
919        private boolean isEncodeExtension(CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theElement) {
920                BaseRuntimeElementDefinition<?> runtimeElementDefinition = getContext().getElementDefinition(theElement.getClass());
921                boolean retVal = true;
922                if (runtimeElementDefinition instanceof BaseRuntimeElementCompositeDefinition) {
923                        BaseRuntimeElementCompositeDefinition definition = (BaseRuntimeElementCompositeDefinition) runtimeElementDefinition;
924                        BaseRuntimeChildDefinition childDef = definition.getChildByName("extension");
925                        CompositeChildElement c = new CompositeChildElement(theParent, childDef, theEncodeContext);
926                        retVal = c.shouldBeEncoded(theContainedResource);
927                }
928                return retVal;
929        }
930
931        @Override
932        public EncodingEnum getEncoding() {
933                return EncodingEnum.JSON;
934        }
935
936        private BaseJsonLikeArray grabJsonArray(BaseJsonLikeObject theObject, String nextName, String thePosition) {
937                BaseJsonLikeValue object = theObject.get(nextName);
938                if (object == null || object.isNull()) {
939                        return null;
940                }
941                if (!object.isArray()) {
942                        throw new DataFormatException(Msg.code(1841) + "Syntax error parsing JSON FHIR structure: Expected ARRAY at element '" + thePosition + "', found '" + object.getJsonType() + "'");
943                }
944                return object.getAsArray();
945        }
946
947        private void parseAlternates(BaseJsonLikeValue theAlternateVal, ParserState<?> theState, String theElementName, String theAlternateName) {
948                if (theAlternateVal == null || theAlternateVal.isNull()) {
949                        return;
950                }
951
952                if (theAlternateVal.isArray()) {
953                        BaseJsonLikeArray array = theAlternateVal.getAsArray();
954                        if (array.size() > 1) {
955                                throw new DataFormatException(Msg.code(1842) + "Unexpected array of length " + array.size() + " (expected 0 or 1) for element: " + theElementName);
956                        }
957                        if (array.size() == 0) {
958                                return;
959                        }
960                        parseAlternates(array.get(0), theState, theElementName, theAlternateName);
961                        return;
962                }
963
964                BaseJsonLikeValue alternateVal = theAlternateVal;
965                if (alternateVal.isObject() == false) {
966                        getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.OBJECT, null, alternateVal.getJsonType(), null);
967                        return;
968                }
969
970                BaseJsonLikeObject alternate = alternateVal.getAsObject();
971
972                for (Iterator<String> keyIter = alternate.keyIterator(); keyIter.hasNext(); ) {
973                        String nextKey = keyIter.next();
974                        BaseJsonLikeValue nextVal = alternate.get(nextKey);
975                        if ("extension".equals(nextKey)) {
976                                boolean isModifier = false;
977                                BaseJsonLikeArray array = nextVal.getAsArray();
978                                parseExtension(theState, array, isModifier);
979                        } else if ("modifierExtension".equals(nextKey)) {
980                                boolean isModifier = true;
981                                BaseJsonLikeArray array = nextVal.getAsArray();
982                                parseExtension(theState, array, isModifier);
983                        } else if ("id".equals(nextKey)) {
984                                if (nextVal.isString()) {
985                                        theState.attributeValue("id", nextVal.getAsString());
986                                } else {
987                                        getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, nextVal.getJsonType(), nextVal.getDataType());
988                                }
989                        } else if ("fhir_comments".equals(nextKey)) {
990                                parseFhirComments(nextVal, theState);
991                        }
992                }
993        }
994
995        private void parseChildren(BaseJsonLikeObject theObject, ParserState<?> theState) {
996                int allUnderscoreNames = 0;
997                int handledUnderscoreNames = 0;
998
999                for (Iterator<String> keyIter = theObject.keyIterator(); keyIter.hasNext(); ) {
1000                        String nextName = keyIter.next();
1001                        if ("resourceType".equals(nextName)) {
1002                                continue;
1003                        } else if ("extension".equals(nextName)) {
1004                                BaseJsonLikeArray array = grabJsonArray(theObject, nextName, "extension");
1005                                parseExtension(theState, array, false);
1006                                continue;
1007                        } else if ("modifierExtension".equals(nextName)) {
1008                                BaseJsonLikeArray array = grabJsonArray(theObject, nextName, "modifierExtension");
1009                                parseExtension(theState, array, true);
1010                                continue;
1011                        } else if (nextName.equals("fhir_comments")) {
1012                                parseFhirComments(theObject.get(nextName), theState);
1013                                continue;
1014                        } else if (nextName.charAt(0) == '_') {
1015                                allUnderscoreNames++;
1016                                continue;
1017                        }
1018
1019                        BaseJsonLikeValue nextVal = theObject.get(nextName);
1020                        String alternateName = '_' + nextName;
1021                        BaseJsonLikeValue alternateVal = theObject.get(alternateName);
1022                        if (alternateVal != null) {
1023                                handledUnderscoreNames++;
1024                        }
1025
1026                        parseChildren(theState, nextName, nextVal, alternateVal, alternateName, false);
1027
1028                }
1029
1030                // if (elementId != null) {
1031                // IBase object = (IBase) theState.getObject();
1032                // if (object instanceof IIdentifiableElement) {
1033                // ((IIdentifiableElement) object).setElementSpecificId(elementId);
1034                // } else if (object instanceof IBaseResource) {
1035                // ((IBaseResource) object).getIdElement().setValue(elementId);
1036                // }
1037                // }
1038
1039                /*
1040                 * This happens if an element has an extension but no actual value. I.e.
1041                 * if a resource has a "_status" element but no corresponding "status"
1042                 * element. This could be used to handle a null value with an extension
1043                 * for example.
1044                 */
1045                if (allUnderscoreNames > handledUnderscoreNames) {
1046                        for (Iterator<String> keyIter = theObject.keyIterator(); keyIter.hasNext(); ) {
1047                                String alternateName = keyIter.next();
1048                                if (alternateName.startsWith("_") && alternateName.length() > 1) {
1049                                        BaseJsonLikeValue nextValue = theObject.get(alternateName);
1050                                        if (nextValue != null) {
1051                                                if (nextValue.isObject()) {
1052                                                        String nextName = alternateName.substring(1);
1053                                                        if (theObject.get(nextName) == null) {
1054                                                                theState.enteringNewElement(null, nextName);
1055                                                                parseAlternates(nextValue, theState, alternateName, alternateName);
1056                                                                theState.endingElement();
1057                                                        }
1058                                                } else {
1059                                                        getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
1060                                                }
1061                                        }
1062                                }
1063                        }
1064                }
1065
1066        }
1067
1068        private void parseChildren(ParserState<?> theState, String theName, BaseJsonLikeValue theJsonVal, BaseJsonLikeValue theAlternateVal, String theAlternateName, boolean theInArray) {
1069                if (theName.equals("id")) {
1070                        if (!theJsonVal.isString()) {
1071                                getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, theJsonVal.getJsonType(), theJsonVal.getDataType());
1072                        }
1073                }
1074
1075                if (theJsonVal.isArray()) {
1076                        BaseJsonLikeArray nextArray = theJsonVal.getAsArray();
1077
1078                        BaseJsonLikeValue alternateVal = theAlternateVal;
1079                        if (alternateVal != null && alternateVal.isArray() == false) {
1080                                getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.ARRAY, null, alternateVal.getJsonType(), null);
1081                                alternateVal = null;
1082                        }
1083
1084                        BaseJsonLikeArray nextAlternateArray = BaseJsonLikeValue.asArray(alternateVal); // could be null
1085                        for (int i = 0; i < nextArray.size(); i++) {
1086                                BaseJsonLikeValue nextObject = nextArray.get(i);
1087                                BaseJsonLikeValue nextAlternate = null;
1088                                if (nextAlternateArray != null && nextAlternateArray.size() >= (i + 1)) {
1089                                        nextAlternate = nextAlternateArray.get(i);
1090                                }
1091                                parseChildren(theState, theName, nextObject, nextAlternate, theAlternateName, true);
1092                        }
1093                } else if (theJsonVal.isObject()) {
1094                        if (!theInArray && theState.elementIsRepeating(theName)) {
1095                                getErrorHandler().incorrectJsonType(null, theName, ValueType.ARRAY, null, ValueType.OBJECT, null);
1096                        }
1097
1098                        theState.enteringNewElement(null, theName);
1099                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1100                        BaseJsonLikeObject nextObject = theJsonVal.getAsObject();
1101                        boolean preResource = false;
1102                        if (theState.isPreResource()) {
1103                                BaseJsonLikeValue resType = nextObject.get("resourceType");
1104                                if (resType == null || !resType.isString()) {
1105                                        throw new DataFormatException(Msg.code(1843) + "Missing required element 'resourceType' from JSON resource object, unable to parse");
1106                                }
1107                                theState.enteringNewElement(null, resType.getAsString());
1108                                preResource = true;
1109                        }
1110                        parseChildren(nextObject, theState);
1111                        if (preResource) {
1112                                theState.endingElement();
1113                        }
1114                        theState.endingElement();
1115                } else if (theJsonVal.isNull()) {
1116                        theState.enteringNewElement(null, theName);
1117                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1118                        theState.endingElement();
1119                } else {
1120                        // must be a SCALAR
1121                        theState.enteringNewElement(null, theName);
1122                        String asString = theJsonVal.getAsString();
1123                        theState.attributeValue("value", asString);
1124                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1125                        theState.endingElement();
1126                }
1127        }
1128
1129        private void parseExtension(ParserState<?> theState, BaseJsonLikeArray theValues, boolean theIsModifier) {
1130                int allUnderscoreNames = 0;
1131                int handledUnderscoreNames = 0;
1132
1133                for (int i = 0; i < theValues.size(); i++) {
1134                        BaseJsonLikeObject nextExtObj = BaseJsonLikeValue.asObject(theValues.get(i));
1135                        BaseJsonLikeValue jsonElement = nextExtObj.get("url");
1136                        String url;
1137                        if (null == jsonElement || !(jsonElement.isScalar())) {
1138                                String parentElementName;
1139                                if (theIsModifier) {
1140                                        parentElementName = "modifierExtension";
1141                                } else {
1142                                        parentElementName = "extension";
1143                                }
1144                                getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName(parentElementName), "url");
1145                                url = null;
1146                        } else {
1147                                url = getExtensionUrl(jsonElement.getAsString());
1148                        }
1149                        theState.enteringNewElementExtension(null, url, theIsModifier, getServerBaseUrl());
1150                        for (Iterator<String> keyIter = nextExtObj.keyIterator(); keyIter.hasNext(); ) {
1151                                String next = keyIter.next();
1152                                if ("url".equals(next)) {
1153                                        continue;
1154                                } else if ("extension".equals(next)) {
1155                                        BaseJsonLikeArray jsonVal = BaseJsonLikeValue.asArray(nextExtObj.get(next));
1156                                        parseExtension(theState, jsonVal, false);
1157                                } else if ("modifierExtension".equals(next)) {
1158                                        BaseJsonLikeArray jsonVal = BaseJsonLikeValue.asArray(nextExtObj.get(next));
1159                                        parseExtension(theState, jsonVal, true);
1160                                } else if (next.charAt(0) == '_') {
1161                                        allUnderscoreNames++;
1162                                        continue;
1163                                } else {
1164                                        BaseJsonLikeValue jsonVal = nextExtObj.get(next);
1165                                        String alternateName = '_' + next;
1166                                        BaseJsonLikeValue alternateVal = nextExtObj.get(alternateName);
1167                                        if (alternateVal != null) {
1168                                                handledUnderscoreNames++;
1169                                        }
1170                                        parseChildren(theState, next, jsonVal, alternateVal, alternateName, false);
1171                                }
1172                        }
1173
1174                        /*
1175                         * This happens if an element has an extension but no actual value. I.e.
1176                         * if a resource has a "_status" element but no corresponding "status"
1177                         * element. This could be used to handle a null value with an extension
1178                         * for example.
1179                         */
1180                        if (allUnderscoreNames > handledUnderscoreNames) {
1181                                for (Iterator<String> keyIter = nextExtObj.keyIterator(); keyIter.hasNext(); ) {
1182                                        String alternateName = keyIter.next();
1183                                        if (alternateName.startsWith("_") && alternateName.length() > 1) {
1184                                                BaseJsonLikeValue nextValue = nextExtObj.get(alternateName);
1185                                                if (nextValue != null) {
1186                                                        if (nextValue.isObject()) {
1187                                                                String nextName = alternateName.substring(1);
1188                                                                if (nextExtObj.get(nextName) == null) {
1189                                                                        theState.enteringNewElement(null, nextName);
1190                                                                        parseAlternates(nextValue, theState, alternateName, alternateName);
1191                                                                        theState.endingElement();
1192                                                                }
1193                                                        } else {
1194                                                                getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
1195                                                        }
1196                                                }
1197                                        }
1198                                }
1199                        }
1200                        theState.endingElement();
1201                }
1202        }
1203
1204        private void parseFhirComments(BaseJsonLikeValue theObject, ParserState<?> theState) {
1205                if (isSupportsFhirComment()) {
1206                        if (theObject.isArray()) {
1207                                BaseJsonLikeArray comments = theObject.getAsArray();
1208                                for (int i = 0; i < comments.size(); i++) {
1209                                        BaseJsonLikeValue nextComment = comments.get(i);
1210                                        if (nextComment.isString()) {
1211                                                String commentText = nextComment.getAsString();
1212                                                if (commentText != null) {
1213                                                        theState.commentPre(commentText);
1214                                                }
1215                                        }
1216                                }
1217                        }
1218                }
1219        }
1220
1221        @Override
1222        public <T extends IBaseResource> T parseResource(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure) throws DataFormatException {
1223
1224                /*****************************************************
1225                 * ************************************************* *
1226                 * ** NOTE: this duplicates most of the code in ** *
1227                 * ** BaseParser.parseResource(Class<T>, Reader). ** *
1228                 * ** Unfortunately, there is no way to avoid ** *
1229                 * ** this without doing some refactoring of the ** *
1230                 * ** BaseParser class. ** *
1231                 * ************************************************* *
1232                 *****************************************************/
1233
1234                /*
1235                 * We do this so that the context can verify that the structure is for
1236                 * the correct FHIR version
1237                 */
1238                if (theResourceType != null) {
1239                        getContext().getResourceDefinition(theResourceType);
1240                }
1241
1242                // Actually do the parse
1243                T retVal = doParseResource(theResourceType, theJsonLikeStructure);
1244
1245                RuntimeResourceDefinition def = getContext().getResourceDefinition(retVal);
1246                if ("Bundle".equals(def.getName())) {
1247
1248                        BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
1249                        BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
1250                        List<IBase> entries = entryChild.getAccessor().getValues(retVal);
1251                        if (entries != null) {
1252                                for (IBase nextEntry : entries) {
1253
1254                                        /**
1255                                         * If Bundle.entry.fullUrl is populated, set the resource ID to that
1256                                         */
1257                                        // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the
1258                                        // fullUrl idPart
1259                                        BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
1260                                        if (fullUrlChild == null) {
1261                                                continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
1262                                        }
1263                                        List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
1264                                        if (fullUrl != null && !fullUrl.isEmpty()) {
1265                                                IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
1266                                                if (value.isEmpty() == false) {
1267                                                        List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry);
1268                                                        if (entryResources != null && entryResources.size() > 0) {
1269                                                                IBaseResource res = (IBaseResource) entryResources.get(0);
1270                                                                String versionId = res.getIdElement().getVersionIdPart();
1271                                                                res.setId(value.getValueAsString());
1272                                                                if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
1273                                                                        res.setId(res.getIdElement().withVersion(versionId));
1274                                                                }
1275                                                        }
1276                                                }
1277                                        }
1278
1279                                }
1280                        }
1281
1282                }
1283
1284                return retVal;
1285        }
1286
1287        @Override
1288        public IBaseResource parseResource(JsonLikeStructure theJsonLikeStructure) throws DataFormatException {
1289                return parseResource(null, theJsonLikeStructure);
1290        }
1291
1292        @Override
1293        public IParser setPrettyPrint(boolean thePrettyPrint) {
1294                myPrettyPrint = thePrettyPrint;
1295                return this;
1296        }
1297
1298        private void write(BaseJsonLikeWriter theEventWriter, String theChildName, Boolean theValue) throws IOException {
1299                if (theValue != null) {
1300                        theEventWriter.write(theChildName, theValue.booleanValue());
1301                }
1302        }
1303
1304        // private void parseExtensionInDstu2Style(boolean theModifier, ParserState<?> theState, String
1305        // theParentExtensionUrl, String theExtensionUrl, JsonArray theValues) {
1306        // String extUrl = UrlUtil.constructAbsoluteUrl(theParentExtensionUrl, theExtensionUrl);
1307        // theState.enteringNewElementExtension(null, extUrl, theModifier);
1308        //
1309        // for (int extIdx = 0; extIdx < theValues.size(); extIdx++) {
1310        // JsonObject nextExt = theValues.getJsonObject(extIdx);
1311        // for (String nextKey : nextExt.keySet()) {
1312        // // if (nextKey.startsWith("value") && nextKey.length() > 5 &&
1313        // // myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(nextKey) != null) {
1314        // JsonElement jsonVal = nextExt.get(nextKey);
1315        // if (jsonVal.getValueType() == ValueType.ARRAY) {
1316        // /*
1317        // * Extension children which are arrays are sub-extensions. Any other value type should be treated as a value.
1318        // */
1319        // JsonArray arrayValue = (JsonArray) jsonVal;
1320        // parseExtensionInDstu2Style(theModifier, theState, extUrl, nextKey, arrayValue);
1321        // } else {
1322        // parseChildren(theState, nextKey, jsonVal, null, null);
1323        // }
1324        // }
1325        // }
1326        //
1327        // theState.endingElement();
1328        // }
1329
1330        private void write(BaseJsonLikeWriter theEventWriter, String theChildName, BigDecimal theDecimalValue) throws IOException {
1331                theEventWriter.write(theChildName, theDecimalValue);
1332        }
1333
1334        private void write(BaseJsonLikeWriter theEventWriter, String theChildName, Integer theValue) throws IOException {
1335                theEventWriter.write(theChildName, theValue);
1336        }
1337
1338        private void writeCommentsPreAndPost(IBase theNextValue, BaseJsonLikeWriter theEventWriter) throws IOException {
1339                if (theNextValue.hasFormatComment()) {
1340                        if (isSupportsFhirComment()) {
1341                                beginArray(theEventWriter, "fhir_comments");
1342                                List<String> pre = theNextValue.getFormatCommentsPre();
1343                                if (pre.isEmpty() == false) {
1344                                        for (String next : pre) {
1345                                                theEventWriter.write(next);
1346                                        }
1347                                }
1348                                List<String> post = theNextValue.getFormatCommentsPost();
1349                                if (post.isEmpty() == false) {
1350                                        for (String next : post) {
1351                                                theEventWriter.write(next);
1352                                        }
1353                                }
1354                                theEventWriter.endArray();
1355                        }
1356                }
1357        }
1358
1359        private void writeExtensionsAsDirectChild(IBaseResource theResource, BaseJsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
1360                                                                                                                        List<HeldExtension> modifierExtensions, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException {
1361                // Write Extensions
1362                if (extensions.isEmpty() == false) {
1363                        theEncodeContext.pushPath("extension", false);
1364                        beginArray(theEventWriter, "extension");
1365                        for (HeldExtension next : extensions) {
1366                                next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource);
1367                        }
1368                        theEventWriter.endArray();
1369                        theEncodeContext.popPath();
1370                }
1371
1372                // Write ModifierExtensions
1373                if (modifierExtensions.isEmpty() == false) {
1374                        theEncodeContext.pushPath("modifierExtension", false);
1375                        beginArray(theEventWriter, "modifierExtension");
1376                        for (HeldExtension next : modifierExtensions) {
1377                                next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource);
1378                        }
1379                        theEventWriter.endArray();
1380                        theEncodeContext.popPath();
1381                }
1382        }
1383
1384        private void writeOptionalTagWithTextNode(BaseJsonLikeWriter theEventWriter, String theElementName, IPrimitiveDatatype<?> thePrimitive) throws IOException {
1385                if (thePrimitive == null) {
1386                        return;
1387                }
1388                String str = thePrimitive.getValueAsString();
1389                writeOptionalTagWithTextNode(theEventWriter, theElementName, str);
1390        }
1391
1392        private void writeOptionalTagWithTextNode(BaseJsonLikeWriter theEventWriter, String theElementName, String theValue) throws IOException {
1393                if (StringUtils.isNotBlank(theValue)) {
1394                        write(theEventWriter, theElementName, theValue);
1395                }
1396        }
1397
1398        private static void write(BaseJsonLikeWriter theWriter, String theName, String theValue) throws IOException {
1399                theWriter.write(theName, theValue);
1400        }
1401
1402        private class HeldExtension implements Comparable<HeldExtension> {
1403
1404                private CompositeChildElement myChildElem;
1405                private RuntimeChildDeclaredExtensionDefinition myDef;
1406                private boolean myModifier;
1407                private IBaseExtension<?, ?> myUndeclaredExtension;
1408                private IBase myValue;
1409                private CompositeChildElement myParent;
1410
1411                public HeldExtension(IBaseExtension<?, ?> theUndeclaredExtension, boolean theModifier, CompositeChildElement theChildElem, CompositeChildElement theParent) {
1412                        assert theUndeclaredExtension != null;
1413                        myUndeclaredExtension = theUndeclaredExtension;
1414                        myModifier = theModifier;
1415                        myChildElem = theChildElem;
1416                        myParent = theParent;
1417                }
1418
1419                public HeldExtension(RuntimeChildDeclaredExtensionDefinition theDef, IBase theValue, CompositeChildElement theChildElem) {
1420                        assert theDef != null;
1421                        assert theValue != null;
1422                        myDef = theDef;
1423                        myValue = theValue;
1424                        myChildElem = theChildElem;
1425                }
1426
1427                @Override
1428                public int compareTo(HeldExtension theArg0) {
1429                        String url1 = myDef != null ? myDef.getExtensionUrl() : myUndeclaredExtension.getUrl();
1430                        String url2 = theArg0.myDef != null ? theArg0.myDef.getExtensionUrl() : theArg0.myUndeclaredExtension.getUrl();
1431                        url1 = defaultString(getExtensionUrl(url1));
1432                        url2 = defaultString(getExtensionUrl(url2));
1433                        return url1.compareTo(url2);
1434                }
1435
1436                private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final BaseJsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException {
1437                        if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
1438                                final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
1439                                final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
1440                                // Undeclared extensions
1441                                extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null, theEncodeContext, theContainedResource);
1442                                // Declared extensions
1443                                if (def != null) {
1444                                        extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent);
1445                                }
1446                                boolean haveContent = false;
1447                                if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) {
1448                                        haveContent = true;
1449                                }
1450                                if (haveContent) {
1451                                        beginObject(theEventWriter, '_' + childName);
1452                                        writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource);
1453                                        theEventWriter.endObject();
1454                                }
1455                        }
1456                }
1457
1458                public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, BaseJsonLikeWriter theEventWriter, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException {
1459                        if (myUndeclaredExtension != null) {
1460                                writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext, theContainedResource);
1461                        } else {
1462                                theEventWriter.beginObject();
1463
1464                                writeCommentsPreAndPost(myValue, theEventWriter);
1465
1466                                JsonParser.write(theEventWriter, "url", getExtensionUrl(myDef.getExtensionUrl()));
1467
1468                                /*
1469                                 * This makes sure that even if the extension contains a reference to a contained
1470                                 * resource which has a HAPI-assigned ID we'll still encode that ID.
1471                                 *
1472                                 * See #327
1473                                 */
1474                                List<? extends IBase> preProcessedValue = preProcessValues(myDef, theResource, Collections.singletonList(myValue), myChildElem, theEncodeContext);
1475
1476                                // // Check for undeclared extensions on the declared extension
1477                                // // (grrrrrr....)
1478                                // if (myValue instanceof ISupportsUndeclaredExtensions) {
1479                                // ISupportsUndeclaredExtensions value = (ISupportsUndeclaredExtensions)myValue;
1480                                // List<ExtensionDt> exts = value.getUndeclaredExtensions();
1481                                // if (exts.size() > 0) {
1482                                // ArrayList<IBase> newValueList = new ArrayList<IBase>();
1483                                // newValueList.addAll(preProcessedValue);
1484                                // newValueList.addAll(exts);
1485                                // preProcessedValue = newValueList;
1486                                // }
1487                                // }
1488
1489                                myValue = preProcessedValue.get(0);
1490
1491                                BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
1492                                if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
1493                                        extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext, theContainedResource);
1494                                } else {
1495                                        String childName = myDef.getChildNameByDatatype(myValue.getClass());
1496                                        encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext);
1497                                        managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext, theContainedResource);
1498                                }
1499
1500                                theEventWriter.endObject();
1501                        }
1502                }
1503
1504                private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, BaseJsonLikeWriter theEventWriter, IBaseExtension<?, ?> ext, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException {
1505                        IBase value = ext.getValue();
1506                        final String extensionUrl = getExtensionUrl(ext.getUrl());
1507
1508                        theEventWriter.beginObject();
1509
1510                        writeCommentsPreAndPost(myUndeclaredExtension, theEventWriter);
1511
1512                        String elementId = getCompositeElementId(ext);
1513                        if (isNotBlank(elementId)) {
1514                                JsonParser.write(theEventWriter, "id", getCompositeElementId(ext));
1515                        }
1516
1517                        if (isBlank(extensionUrl)) {
1518                                ParseLocation loc = new ParseLocation(theEncodeContext.toString());
1519                                getErrorHandler().missingRequiredElement(loc, "url");
1520                        }
1521
1522                        JsonParser.write(theEventWriter, "url", extensionUrl);
1523
1524                        boolean noValue = value == null || value.isEmpty();
1525                        if (noValue && ext.getExtension().isEmpty()) {
1526
1527                                ParseLocation loc = new ParseLocation(theEncodeContext.toString());
1528                                getErrorHandler().missingRequiredElement(loc, "value");
1529                                ourLog.debug("Extension with URL[{}] has no value", extensionUrl);
1530
1531                        } else {
1532
1533                                if (!noValue && !ext.getExtension().isEmpty()) {
1534                                        ParseLocation loc = new ParseLocation(theEncodeContext.toString());
1535                                        getErrorHandler().extensionContainsValueAndNestedExtensions(loc);
1536                                }
1537
1538                                // Write child extensions
1539                                if (!ext.getExtension().isEmpty()) {
1540
1541                                        if (myModifier) {
1542                                                beginArray(theEventWriter, "modifierExtension");
1543                                        } else {
1544                                                beginArray(theEventWriter, "extension");
1545                                        }
1546
1547                                        for (Object next : ext.getExtension()) {
1548                                                writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next, theEncodeContext, theContainedResource);
1549                                        }
1550                                        theEventWriter.endArray();
1551
1552                                }
1553
1554                                // Write value
1555                                if (!noValue) {
1556                                        theEncodeContext.pushPath("value", false);
1557
1558                                        /*
1559                                         * Pre-process value - This is called in case the value is a reference
1560                                         * since we might modify the text
1561                                         */
1562                                        value = preProcessValues(myDef, theResource, Collections.singletonList(value), myChildElem, theEncodeContext).get(0);
1563
1564                                        RuntimeChildUndeclaredExtensionDefinition extDef = getContext().getRuntimeChildUndeclaredExtensionDefinition();
1565                                        String childName = extDef.getChildNameByDatatype(value.getClass());
1566                                        if (childName == null) {
1567                                                childName = "value" + WordUtils.capitalize(getContext().getElementDefinition(value.getClass()).getName());
1568                                        }
1569                                        BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
1570                                        if (childDef == null) {
1571                                                throw new ConfigurationException(Msg.code(1844) + "Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
1572                                        }
1573                                        encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, false, myParent, false, theEncodeContext);
1574                                        managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext, theContainedResource);
1575
1576                                        theEncodeContext.popPath();
1577                                }
1578                        }
1579
1580                        // theEventWriter.name(myUndeclaredExtension.get);
1581
1582                        theEventWriter.endObject();
1583                }
1584        }
1585}