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