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