View Javadoc
1   package ca.uhn.fhir.parser;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 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) 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, false);
151 		theEventWriter.flush();
152 	}
153 
154 	@Override
155 	protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException {
156 		JsonLikeWriter eventWriter = createJsonWriter(theWriter);
157 		doEncodeResourceToJsonLikeWriter(theResource, eventWriter);
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, boolean theSubResource, CompositeChildElement theChildElem,
196 																 boolean theForceEmpty) 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, theSubResource, theChildElem);
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, false, fixContainedResourceId(resourceId.getValue()));
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 				encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, true);
315 				break;
316 			case UNDECL_EXT:
317 			default:
318 				throw new IllegalStateException("Should not have this state here: " + theChildDef.getChildType().name());
319 		}
320 
321 	}
322 
323 	private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter,
324 																				 boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException {
325 
326 		{
327 			String elementId = getCompositeElementId(theElement);
328 			if (isNotBlank(elementId)) {
329 				write(theEventWriter, "id", elementId);
330 			}
331 		}
332 
333 		boolean haveWrittenExtensions = false;
334 		for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
335 
336 			BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
337 
338 			if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension")
339 				|| nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
340 				if (!haveWrittenExtensions) {
341 					extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, myContext.getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent);
342 					haveWrittenExtensions = true;
343 				}
344 				continue;
345 			}
346 
347 			if (nextChild instanceof RuntimeChildNarrativeDefinition) {
348 				INarrativeGenerator gen = myContext.getNarrativeGenerator();
349 				if (gen != null) {
350 					INarrative narr;
351 					if (theResource instanceof IResource) {
352 						narr = ((IResource) theResource).getText();
353 					} else if (theResource instanceof IDomainResource) {
354 						narr = ((IDomainResource) theResource).getText();
355 					} else {
356 						narr = null;
357 					}
358 					if (narr != null && narr.isEmpty()) {
359 						gen.generateNarrative(myContext, theResource, narr);
360 						if (!narr.isEmpty()) {
361 							RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
362 							String childName = nextChild.getChildNameByDatatype(child.getDatatype());
363 							BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
364 							encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, theSubResource, nextChildElem, false);
365 							continue;
366 						}
367 					}
368 				}
369 			} else if (nextChild instanceof RuntimeChildContainedResources) {
370 				String childName = nextChild.getValidChildNames().iterator().next();
371 				BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
372 				encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, theSubResource, nextChildElem, false);
373 				continue;
374 			}
375 
376 			List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
377 			values = super.preProcessValues(nextChild, theResource, values, nextChildElem);
378 
379 			if (values == null || values.isEmpty()) {
380 				continue;
381 			}
382 
383 			String currentChildName = null;
384 			boolean inArray = false;
385 
386 			ArrayList<ArrayList<HeldExtension>> extensions = new ArrayList<ArrayList<HeldExtension>>(0);
387 			ArrayList<ArrayList<HeldExtension>> modifierExtensions = new ArrayList<ArrayList<HeldExtension>>(0);
388 			ArrayList<ArrayList<String>> comments = new ArrayList<ArrayList<String>>(0);
389 			ArrayList<String> ids = new ArrayList<String>(0);
390 
391 			int valueIdx = 0;
392 			for (IBase nextValue : values) {
393 
394 				if (nextValue == null || nextValue.isEmpty()) {
395 					if (nextValue instanceof BaseContainedDt) {
396 						if (theContainedResource || getContainedResources().isEmpty()) {
397 							continue;
398 						}
399 					} else {
400 						continue;
401 					}
402 				}
403 
404 				BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
405 				if (childNameAndDef == null) {
406 					continue;
407 				}
408 
409 				String childName = childNameAndDef.getChildName();
410 				BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
411 				boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
412 
413 				if ((childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && theContainedResource) {
414 					continue;
415 				}
416 
417 				boolean force = false;
418 				if (primitive) {
419 					if (nextValue instanceof ISupportsUndeclaredExtensions) {
420 						List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions();
421 						force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent);
422 
423 						ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions();
424 						force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent);
425 					} else {
426 						if (nextValue instanceof IBaseHasExtensions) {
427 							IBaseHasExtensions element = (IBaseHasExtensions) nextValue;
428 							List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
429 							force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent);
430 						}
431 						if (nextValue instanceof IBaseHasModifierExtensions) {
432 							IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue;
433 							List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
434 							force |= addToHeldExtensions(valueIdx, ext, extensions, true, nextChildElem, theParent);
435 						}
436 					}
437 					if (nextValue.hasFormatComment()) {
438 						force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPre(), comments);
439 						force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPost(), comments);
440 					}
441 					String elementId = getCompositeElementId(nextValue);
442 					if (isNotBlank(elementId)) {
443 						force = true;
444 						addToHeldIds(valueIdx, ids, elementId);
445 					}
446 				}
447 
448 				if (currentChildName == null || !currentChildName.equals(childName)) {
449 					if (inArray) {
450 						theEventWriter.endArray();
451 					}
452 					if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
453 						beginArray(theEventWriter, childName);
454 						inArray = true;
455 						encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource, nextChildElem, force);
456 					} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
457 						// suppress narratives from contained resources
458 					} else {
459 						encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource, nextChildElem, false);
460 					}
461 					currentChildName = childName;
462 				} else {
463 					encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource, nextChildElem, force);
464 				}
465 
466 				valueIdx++;
467 			}
468 
469 			if (inArray) {
470 				theEventWriter.endArray();
471 			}
472 
473 			if (!extensions.isEmpty() || !modifierExtensions.isEmpty() || !comments.isEmpty()) {
474 				if (inArray) {
475 					// If this is a repeatable field, the extensions go in an array too
476 					beginArray(theEventWriter, '_' + currentChildName);
477 				} else {
478 					beginObject(theEventWriter, '_' + currentChildName);
479 				}
480 
481 				for (int i = 0; i < valueIdx; i++) {
482 					boolean haveContent = false;
483 
484 					List<HeldExtension> heldExts = Collections.emptyList();
485 					List<HeldExtension> heldModExts = Collections.emptyList();
486 					if (extensions.size() > i && extensions.get(i) != null && extensions.get(i).isEmpty() == false) {
487 						haveContent = true;
488 						heldExts = extensions.get(i);
489 					}
490 
491 					if (modifierExtensions.size() > i && modifierExtensions.get(i) != null && modifierExtensions.get(i).isEmpty() == false) {
492 						haveContent = true;
493 						heldModExts = modifierExtensions.get(i);
494 					}
495 
496 					ArrayList<String> nextComments;
497 					if (comments.size() > i) {
498 						nextComments = comments.get(i);
499 					} else {
500 						nextComments = null;
501 					}
502 					if (nextComments != null && nextComments.isEmpty() == false) {
503 						haveContent = true;
504 					}
505 
506 					String elementId = null;
507 					if (ids.size() > i) {
508 						elementId = ids.get(i);
509 						haveContent |= isNotBlank(elementId);
510 					}
511 
512 					if (!haveContent) {
513 						theEventWriter.writeNull();
514 					} else {
515 						if (inArray) {
516 							theEventWriter.beginObject();
517 						}
518 						if (isNotBlank(elementId)) {
519 							write(theEventWriter, "id", elementId);
520 						}
521 						if (nextComments != null && !nextComments.isEmpty()) {
522 							beginArray(theEventWriter, "fhir_comments");
523 							for (String next : nextComments) {
524 								theEventWriter.write(next);
525 							}
526 							theEventWriter.endArray();
527 						}
528 						writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts);
529 						if (inArray) {
530 							theEventWriter.endObject();
531 						}
532 					}
533 				}
534 
535 				if (inArray) {
536 					theEventWriter.endArray();
537 				} else {
538 					theEventWriter.endObject();
539 				}
540 			}
541 		}
542 	}
543 
544 	private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, boolean theSubResource,
545 																	  CompositeChildElement theParent) throws IOException, DataFormatException {
546 
547 		writeCommentsPreAndPost(theNextValue, theEventWriter);
548 		encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent);
549 	}
550 
551 	@Override
552 	public void encodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException {
553 		Validate.notNull(theResource, "theResource can not be null");
554 		Validate.notNull(theJsonLikeWriter, "theJsonLikeWriter can not be null");
555 
556 		if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
557 			throw new IllegalArgumentException(
558 				"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
559 		}
560 
561 		doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter);
562 	}
563 
564 	private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
565 																 boolean theContainedResource, boolean theSubResource) throws IOException {
566 		IIdType resourceId = null;
567 		// if (theResource instanceof IResource) {
568 		// IResource res = (IResource) theResource;
569 		// if (StringUtils.isNotBlank(res.getId().getIdPart())) {
570 		// if (theContainedResource) {
571 		// resourceId = res.getId().getIdPart();
572 		// } else if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
573 		// resourceId = res.getId().getIdPart();
574 		// }
575 		// }
576 		// } else if (theResource instanceof IAnyResource) {
577 		// IAnyResource res = (IAnyResource) theResource;
578 		// if (/* theContainedResource && */StringUtils.isNotBlank(res.getIdElement().getIdPart())) {
579 		// resourceId = res.getIdElement().getIdPart();
580 		// }
581 		// }
582 
583 		if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
584 			resourceId = theResource.getIdElement();
585 			if (theResource.getIdElement().getValue().startsWith("urn:")) {
586 				resourceId = null;
587 			}
588 		}
589 
590 		if (!theContainedResource) {
591 			if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
592 				resourceId = null;
593 			} else if (!theSubResource && getEncodeForceResourceId() != null) {
594 				resourceId = getEncodeForceResourceId();
595 			}
596 		}
597 
598 		encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, theSubResource, resourceId);
599 	}
600 
601 	private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
602 																 boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException {
603 		if (!theContainedResource) {
604 			super.containResourcesForEncoding(theResource);
605 		}
606 
607 		RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
608 
609 		if (theObjectNameOrNull == null) {
610 			theEventWriter.beginObject();
611 		} else {
612 			beginObject(theEventWriter, theObjectNameOrNull);
613 		}
614 
615 		write(theEventWriter, "resourceType", resDef.getName());
616 		if (theResourceId != null && theResourceId.hasIdPart()) {
617 			write(theEventWriter, "id", theResourceId.getIdPart());
618 			final List<HeldExtension> extensions = new ArrayList<>(0);
619 			final List<HeldExtension> modifierExtensions = new ArrayList<>(0);
620 			// Undeclared extensions
621 			extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null);
622 			boolean haveExtension = false;
623 			if (!extensions.isEmpty()) {
624 				haveExtension = true;
625 			}
626 
627 			if (theResourceId.hasFormatComment() || haveExtension) {
628 				beginObject(theEventWriter, "_id");
629 				if (theResourceId.hasFormatComment()) {
630 					writeCommentsPreAndPost(theResourceId, theEventWriter);
631 				}
632 				if (haveExtension) {
633 					writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
634 				}
635 				theEventWriter.endObject();
636 			}
637 		}
638 
639 		if (theResource instanceof IResource) {
640 			IResource resource = (IResource) theResource;
641 			// Object securityLabelRawObj =
642 
643 			List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
644 			List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
645 			profiles = super.getProfileTagsForEncoding(resource, profiles);
646 
647 			TagList tags = getMetaTagsForEncoding(resource);
648 			InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
649 			IdDt resourceId = resource.getId();
650 			String versionIdPart = resourceId.getVersionIdPart();
651 			if (isBlank(versionIdPart)) {
652 				versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
653 			}
654 			List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource);
655 
656 			if (super.shouldEncodeResourceMeta(resource) && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) || !extensionMetadataKeys.isEmpty()) {
657 				beginObject(theEventWriter, "meta");
658 				writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart);
659 				writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated);
660 
661 				if (profiles != null && profiles.isEmpty() == false) {
662 					beginArray(theEventWriter, "profile");
663 					for (IIdType profile : profiles) {
664 						if (profile != null && isNotBlank(profile.getValue())) {
665 							theEventWriter.write(profile.getValue());
666 						}
667 					}
668 					theEventWriter.endArray();
669 				}
670 
671 				if (securityLabels.isEmpty() == false) {
672 					beginArray(theEventWriter, "security");
673 					for (BaseCodingDt securityLabel : securityLabels) {
674 						theEventWriter.beginObject();
675 						encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
676 						theEventWriter.endObject();
677 					}
678 					theEventWriter.endArray();
679 				}
680 
681 				if (tags != null && tags.isEmpty() == false) {
682 					beginArray(theEventWriter, "tag");
683 					for (Tag tag : tags) {
684 						if (tag.isEmpty()) {
685 							continue;
686 						}
687 						theEventWriter.beginObject();
688 						writeOptionalTagWithTextNode(theEventWriter, "system", tag.getScheme());
689 						writeOptionalTagWithTextNode(theEventWriter, "code", tag.getTerm());
690 						writeOptionalTagWithTextNode(theEventWriter, "display", tag.getLabel());
691 						theEventWriter.endObject();
692 					}
693 					theEventWriter.endArray();
694 				}
695 
696 				addExtensionMetadata(theResDef, theResource, theContainedResource, theSubResource, extensionMetadataKeys, resDef, theEventWriter);
697 
698 				theEventWriter.endObject(); // end meta
699 			}
700 		}
701 
702 		encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
703 
704 		theEventWriter.endObject();
705 	}
706 
707 
708 	private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource,
709 												 boolean theContainedResource, boolean theSubResource,
710 												 List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys,
711 												 RuntimeResourceDefinition resDef,
712 												 JsonLikeWriter theEventWriter) throws IOException {
713 		if (extensionMetadataKeys.isEmpty()) {
714 			return;
715 		}
716 
717 		ExtensionDt metaResource = new ExtensionDt();
718 		for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) {
719 			metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue());
720 		}
721 		encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
722 	}
723 
724 	/**
725 	 * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object
726 	 * called _name): resource extensions, and extension extensions
727 	 */
728 	private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef,
729 																		 IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent) throws IOException {
730 		List<HeldExtension> extensions = new ArrayList<>(0);
731 		List<HeldExtension> modifierExtensions = new ArrayList<>(0);
732 
733 		// Undeclared extensions
734 		extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent);
735 
736 		// Declared extensions
737 		if (theElementDef != null) {
738 			extractDeclaredExtensions(theElement, theElementDef, extensions, modifierExtensions, theChildElem);
739 		}
740 
741 		// Write the extensions
742 		writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
743 	}
744 
745 	private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions,
746 														CompositeChildElement theChildElem) {
747 		for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) {
748 			for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
749 				if (nextValue != null) {
750 					if (nextValue.isEmpty()) {
751 						continue;
752 					}
753 					extensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
754 				}
755 			}
756 		}
757 		for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) {
758 			for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
759 				if (nextValue != null) {
760 					if (nextValue.isEmpty()) {
761 						continue;
762 					}
763 					modifierExtensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
764 				}
765 			}
766 		}
767 	}
768 
769 	private void extractUndeclaredExtensions(IBase theElement, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, CompositeChildElement theChildElem,
770 														  CompositeChildElement theParent) {
771 		if (theElement instanceof ISupportsUndeclaredExtensions) {
772 			ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement;
773 			List<ExtensionDt> ext = element.getUndeclaredExtensions();
774 			for (ExtensionDt next : ext) {
775 				if (next == null || next.isEmpty()) {
776 					continue;
777 				}
778 				extensions.add(new HeldExtension(next, false, theChildElem, theParent));
779 			}
780 
781 			ext = element.getUndeclaredModifierExtensions();
782 			for (ExtensionDt next : ext) {
783 				if (next == null || next.isEmpty()) {
784 					continue;
785 				}
786 				modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent));
787 			}
788 		} else {
789 			if (theElement instanceof IBaseHasExtensions) {
790 				IBaseHasExtensions element = (IBaseHasExtensions) theElement;
791 				List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
792 				for (IBaseExtension<?, ?> next : ext) {
793 					if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
794 						continue;
795 					}
796 					extensions.add(new HeldExtension(next, false, theChildElem, theParent));
797 				}
798 			}
799 			if (theElement instanceof IBaseHasModifierExtensions) {
800 				IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) theElement;
801 				List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
802 				for (IBaseExtension<?, ?> next : ext) {
803 					if (next == null || next.isEmpty()) {
804 						continue;
805 					}
806 					modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent));
807 				}
808 			}
809 		}
810 	}
811 
812 	@Override
813 	public EncodingEnum getEncoding() {
814 		return EncodingEnum.JSON;
815 	}
816 
817 	private JsonLikeArray grabJsonArray(JsonLikeObject theObject, String nextName, String thePosition) {
818 		JsonLikeValue object = theObject.get(nextName);
819 		if (object == null || object.isNull()) {
820 			return null;
821 		}
822 		if (!object.isArray()) {
823 			throw new DataFormatException("Syntax error parsing JSON FHIR structure: Expected ARRAY at element '" + thePosition + "', found '" + object.getJsonType() + "'");
824 		}
825 		return object.getAsArray();
826 	}
827 
828 	// private JsonObject parse(Reader theReader) {
829 	//
830 	// PushbackReader pbr = new PushbackReader(theReader);
831 	// JsonObject object;
832 	// try {
833 	// while(true) {
834 	// int nextInt;
835 	// nextInt = pbr.read();
836 	// if (nextInt == -1) {
837 	// throw new DataFormatException("Did not find any content to parse");
838 	// }
839 	// if (nextInt == '{') {
840 	// pbr.unread('{');
841 	// break;
842 	// }
843 	// if (Character.isWhitespace(nextInt)) {
844 	// continue;
845 	// }
846 	// throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')");
847 	// }
848 	//
849 	// Gson gson = newGson();
850 	//
851 	// object = gson.fromJson(pbr, JsonObject.class);
852 	// } catch (Exception e) {
853 	// throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e);
854 	// }
855 	//
856 	// return object;
857 	// }
858 
859 	private void parseAlternates(JsonLikeValue theAlternateVal, ParserState<?> theState, String theElementName, String theAlternateName) {
860 		if (theAlternateVal == null || theAlternateVal.isNull()) {
861 			return;
862 		}
863 
864 		if (theAlternateVal.isArray()) {
865 			JsonLikeArray array = theAlternateVal.getAsArray();
866 			if (array.size() > 1) {
867 				throw new DataFormatException("Unexpected array of length " + array.size() + " (expected 0 or 1) for element: " + theElementName);
868 			}
869 			if (array.size() == 0) {
870 				return;
871 			}
872 			parseAlternates(array.get(0), theState, theElementName, theAlternateName);
873 			return;
874 		}
875 
876 		JsonLikeValue alternateVal = theAlternateVal;
877 		if (alternateVal.isObject() == false) {
878 			getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.OBJECT, null, alternateVal.getJsonType(), null);
879 			return;
880 		}
881 
882 		JsonLikeObject alternate = alternateVal.getAsObject();
883 		for (String nextKey : alternate.keySet()) {
884 			JsonLikeValue nextVal = alternate.get(nextKey);
885 			if ("extension".equals(nextKey)) {
886 				boolean isModifier = false;
887 				JsonLikeArray array = nextVal.getAsArray();
888 				parseExtension(theState, array, isModifier);
889 			} else if ("modifierExtension".equals(nextKey)) {
890 				boolean isModifier = true;
891 				JsonLikeArray array = nextVal.getAsArray();
892 				parseExtension(theState, array, isModifier);
893 			} else if ("id".equals(nextKey)) {
894 				if (nextVal.isString()) {
895 					theState.attributeValue("id", nextVal.getAsString());
896 				} else {
897 					getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, nextVal.getJsonType(), nextVal.getDataType());
898 				}
899 			} else if ("fhir_comments".equals(nextKey)) {
900 				parseFhirComments(nextVal, theState);
901 			}
902 		}
903 	}
904 
905 	private void parseChildren(JsonLikeObject theObject, ParserState<?> theState) {
906 		Set<String> keySet = theObject.keySet();
907 
908 		int allUnderscoreNames = 0;
909 		int handledUnderscoreNames = 0;
910 
911 		for (String nextName : keySet) {
912 			if ("resourceType".equals(nextName)) {
913 				continue;
914 			} else if ("extension".equals(nextName)) {
915 				JsonLikeArray array = grabJsonArray(theObject, nextName, "extension");
916 				parseExtension(theState, array, false);
917 				continue;
918 			} else if ("modifierExtension".equals(nextName)) {
919 				JsonLikeArray array = grabJsonArray(theObject, nextName, "modifierExtension");
920 				parseExtension(theState, array, true);
921 				continue;
922 			} else if (nextName.equals("fhir_comments")) {
923 				parseFhirComments(theObject.get(nextName), theState);
924 				continue;
925 			} else if (nextName.charAt(0) == '_') {
926 				allUnderscoreNames++;
927 				continue;
928 			}
929 
930 			JsonLikeValue nextVal = theObject.get(nextName);
931 			String alternateName = '_' + nextName;
932 			JsonLikeValue alternateVal = theObject.get(alternateName);
933 			if (alternateVal != null) {
934 				handledUnderscoreNames++;
935 			}
936 
937 			parseChildren(theState, nextName, nextVal, alternateVal, alternateName, false);
938 
939 		}
940 
941 		// if (elementId != null) {
942 		// IBase object = (IBase) theState.getObject();
943 		// if (object instanceof IIdentifiableElement) {
944 		// ((IIdentifiableElement) object).setElementSpecificId(elementId);
945 		// } else if (object instanceof IBaseResource) {
946 		// ((IBaseResource) object).getIdElement().setValue(elementId);
947 		// }
948 		// }
949 
950 		/*
951 		 * This happens if an element has an extension but no actual value. I.e.
952 		 * if a resource has a "_status" element but no corresponding "status"
953 		 * element. This could be used to handle a null value with an extension
954 		 * for example.
955 		 */
956 		if (allUnderscoreNames > handledUnderscoreNames) {
957 			for (String alternateName : keySet) {
958 				if (alternateName.startsWith("_") && alternateName.length() > 1) {
959 					JsonLikeValue nextValue = theObject.get(alternateName);
960 					if (nextValue != null) {
961 						if (nextValue.isObject()) {
962 							String nextName = alternateName.substring(1);
963 							if (theObject.get(nextName) == null) {
964 								theState.enteringNewElement(null, nextName);
965 								parseAlternates(nextValue, theState, alternateName, alternateName);
966 								theState.endingElement();
967 							}
968 						} else {
969 							getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
970 						}
971 					}
972 				}
973 			}
974 		}
975 
976 	}
977 
978 	private void parseChildren(ParserState<?> theState, String theName, JsonLikeValue theJsonVal, JsonLikeValue theAlternateVal, String theAlternateName, boolean theInArray) {
979 		if (theName.equals("id")) {
980 			if (!theJsonVal.isString()) {
981 				getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, theJsonVal.getJsonType(), theJsonVal.getDataType());
982 			}
983 		}
984 
985 		if (theJsonVal.isArray()) {
986 			JsonLikeArray nextArray = theJsonVal.getAsArray();
987 
988 			JsonLikeValue alternateVal = theAlternateVal;
989 			if (alternateVal != null && alternateVal.isArray() == false) {
990 				getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.ARRAY, null, alternateVal.getJsonType(), null);
991 				alternateVal = null;
992 			}
993 
994 			JsonLikeArray nextAlternateArray = JsonLikeValue.asArray(alternateVal); // could be null
995 			for (int i = 0; i < nextArray.size(); i++) {
996 				JsonLikeValue nextObject = nextArray.get(i);
997 				JsonLikeValue nextAlternate = null;
998 				if (nextAlternateArray != null && nextAlternateArray.size() >= (i + 1)) {
999 					nextAlternate = nextAlternateArray.get(i);
1000 				}
1001 				parseChildren(theState, theName, nextObject, nextAlternate, theAlternateName, true);
1002 			}
1003 		} else if (theJsonVal.isObject()) {
1004 			if (!theInArray && theState.elementIsRepeating(theName)) {
1005 				getErrorHandler().incorrectJsonType(null, theName, ValueType.ARRAY, null, ValueType.OBJECT, null);
1006 			}
1007 
1008 			theState.enteringNewElement(null, theName);
1009 			parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1010 			JsonLikeObject nextObject = theJsonVal.getAsObject();
1011 			boolean preResource = false;
1012 			if (theState.isPreResource()) {
1013 				JsonLikeValue resType = nextObject.get("resourceType");
1014 				if (resType == null || !resType.isString()) {
1015 					throw new DataFormatException("Missing required element 'resourceType' from JSON resource object, unable to parse");
1016 				}
1017 				theState.enteringNewElement(null, resType.getAsString());
1018 				preResource = true;
1019 			}
1020 			parseChildren(nextObject, theState);
1021 			if (preResource) {
1022 				theState.endingElement();
1023 			}
1024 			theState.endingElement();
1025 		} else if (theJsonVal.isNull()) {
1026 			theState.enteringNewElement(null, theName);
1027 			parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1028 			theState.endingElement();
1029 		} else {
1030 			// must be a SCALAR
1031 			theState.enteringNewElement(null, theName);
1032 			theState.attributeValue("value", theJsonVal.getAsString());
1033 			parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1034 			theState.endingElement();
1035 		}
1036 	}
1037 
1038 	private void parseExtension(ParserState<?> theState, JsonLikeArray theValues, boolean theIsModifier) {
1039 		int allUnderscoreNames = 0;
1040 		int handledUnderscoreNames = 0;
1041 
1042 		for (int i = 0; i < theValues.size(); i++) {
1043 			JsonLikeObject nextExtObj = JsonLikeValue.asObject(theValues.get(i));
1044 			JsonLikeValue jsonElement = nextExtObj.get("url");
1045 			String url;
1046 			if (null == jsonElement || !(jsonElement.isScalar())) {
1047 				String parentElementName;
1048 				if (theIsModifier) {
1049 					parentElementName = "modifierExtension";
1050 				} else {
1051 					parentElementName = "extension";
1052 				}
1053 				getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName(parentElementName), "url");
1054 				url = null;
1055 			} else {
1056 				url = getExtensionUrl(jsonElement.getAsString());
1057 			}
1058 			theState.enteringNewElementExtension(null, url, theIsModifier, getServerBaseUrl());
1059 			for (String next : nextExtObj.keySet()) {
1060 				if ("url".equals(next)) {
1061 					continue;
1062 				} else if ("extension".equals(next)) {
1063 					JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
1064 					parseExtension(theState, jsonVal, false);
1065 				} else if ("modifierExtension".equals(next)) {
1066 					JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
1067 					parseExtension(theState, jsonVal, true);
1068 				} else if (next.charAt(0) == '_') {
1069 					allUnderscoreNames++;
1070 					continue;
1071 				} else {
1072 					JsonLikeValue jsonVal = nextExtObj.get(next);
1073 					String alternateName = '_' + next;
1074 					JsonLikeValue alternateVal = nextExtObj.get(alternateName);
1075 					if (alternateVal != null) {
1076 						handledUnderscoreNames++;
1077 					}
1078 					parseChildren(theState, next, jsonVal, alternateVal, alternateName, false);
1079 				}
1080 			}
1081 
1082 			/*
1083 			 * This happens if an element has an extension but no actual value. I.e.
1084 			 * if a resource has a "_status" element but no corresponding "status"
1085 			 * element. This could be used to handle a null value with an extension
1086 			 * for example.
1087 			 */
1088 			if (allUnderscoreNames > handledUnderscoreNames) {
1089 				for (String alternateName : nextExtObj.keySet()) {
1090 					if (alternateName.startsWith("_") && alternateName.length() > 1) {
1091 						JsonLikeValue nextValue = nextExtObj.get(alternateName);
1092 						if (nextValue != null) {
1093 							if (nextValue.isObject()) {
1094 								String nextName = alternateName.substring(1);
1095 								if (nextExtObj.get(nextName) == null) {
1096 									theState.enteringNewElement(null, nextName);
1097 									parseAlternates(nextValue, theState, alternateName, alternateName);
1098 									theState.endingElement();
1099 								}
1100 							} else {
1101 								getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
1102 							}
1103 						}
1104 					}
1105 				}
1106 			}
1107 			theState.endingElement();
1108 		}
1109 	}
1110 
1111 	private void parseFhirComments(JsonLikeValue theObject, ParserState<?> theState) {
1112 		if (theObject.isArray()) {
1113 			JsonLikeArray comments = theObject.getAsArray();
1114 			for (int i = 0; i < comments.size(); i++) {
1115 				JsonLikeValue nextComment = comments.get(i);
1116 				if (nextComment.isString()) {
1117 					String commentText = nextComment.getAsString();
1118 					if (commentText != null) {
1119 						theState.commentPre(commentText);
1120 					}
1121 				}
1122 			}
1123 		}
1124 	}
1125 
1126 	@Override
1127 	public <T extends IBaseResource> T parseResource(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure) throws DataFormatException {
1128 
1129 		/*****************************************************
1130 		 * ************************************************* *
1131 		 * ** NOTE: this duplicates most of the code in ** *
1132 		 * ** BaseParser.parseResource(Class<T>, Reader). ** *
1133 		 * ** Unfortunately, there is no way to avoid ** *
1134 		 * ** this without doing some refactoring of the ** *
1135 		 * ** BaseParser class. ** *
1136 		 * ************************************************* *
1137 		 *****************************************************/
1138 
1139 		/*
1140 		 * We do this so that the context can verify that the structure is for
1141 		 * the correct FHIR version
1142 		 */
1143 		if (theResourceType != null) {
1144 			myContext.getResourceDefinition(theResourceType);
1145 		}
1146 
1147 		// Actually do the parse
1148 		T retVal = doParseResource(theResourceType, theJsonLikeStructure);
1149 
1150 		RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal);
1151 		if ("Bundle".equals(def.getName())) {
1152 
1153 			BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
1154 			BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
1155 			List<IBase> entries = entryChild.getAccessor().getValues(retVal);
1156 			if (entries != null) {
1157 				for (IBase nextEntry : entries) {
1158 
1159 					/**
1160 					 * If Bundle.entry.fullUrl is populated, set the resource ID to that
1161 					 */
1162 					// TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the
1163 					// fullUrl idPart
1164 					BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
1165 					if (fullUrlChild == null) {
1166 						continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
1167 					}
1168 					List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
1169 					if (fullUrl != null && !fullUrl.isEmpty()) {
1170 						IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
1171 						if (value.isEmpty() == false) {
1172 							List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry);
1173 							if (entryResources != null && entryResources.size() > 0) {
1174 								IBaseResource res = (IBaseResource) entryResources.get(0);
1175 								String versionId = res.getIdElement().getVersionIdPart();
1176 								res.setId(value.getValueAsString());
1177 								if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
1178 									res.setId(res.getIdElement().withVersion(versionId));
1179 								}
1180 							}
1181 						}
1182 					}
1183 
1184 				}
1185 			}
1186 
1187 		}
1188 
1189 		return retVal;
1190 	}
1191 
1192 	@Override
1193 	public IBaseResource parseResource(JsonLikeStructure theJsonLikeStructure) throws DataFormatException {
1194 		return parseResource(null, theJsonLikeStructure);
1195 	}
1196 
1197 	@Override
1198 	public IParser setPrettyPrint(boolean thePrettyPrint) {
1199 		myPrettyPrint = thePrettyPrint;
1200 		return this;
1201 	}
1202 
1203 	private void write(JsonLikeWriter theEventWriter, String theChildName, Boolean theValue) throws IOException {
1204 		if (theValue != null) {
1205 			theEventWriter.write(theChildName, theValue.booleanValue());
1206 		}
1207 	}
1208 
1209 	// private void parseExtensionInDstu2Style(boolean theModifier, ParserState<?> theState, String
1210 	// theParentExtensionUrl, String theExtensionUrl, JsonArray theValues) {
1211 	// String extUrl = UrlUtil.constructAbsoluteUrl(theParentExtensionUrl, theExtensionUrl);
1212 	// theState.enteringNewElementExtension(null, extUrl, theModifier);
1213 	//
1214 	// for (int extIdx = 0; extIdx < theValues.size(); extIdx++) {
1215 	// JsonObject nextExt = theValues.getJsonObject(extIdx);
1216 	// for (String nextKey : nextExt.keySet()) {
1217 	// // if (nextKey.startsWith("value") && nextKey.length() > 5 &&
1218 	// // myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(nextKey) != null) {
1219 	// JsonElement jsonVal = nextExt.get(nextKey);
1220 	// if (jsonVal.getValueType() == ValueType.ARRAY) {
1221 	// /*
1222 	// * Extension children which are arrays are sub-extensions. Any other value type should be treated as a value.
1223 	// */
1224 	// JsonArray arrayValue = (JsonArray) jsonVal;
1225 	// parseExtensionInDstu2Style(theModifier, theState, extUrl, nextKey, arrayValue);
1226 	// } else {
1227 	// parseChildren(theState, nextKey, jsonVal, null, null);
1228 	// }
1229 	// }
1230 	// }
1231 	//
1232 	// theState.endingElement();
1233 	// }
1234 
1235 	private void write(JsonLikeWriter theEventWriter, String theChildName, BigDecimal theDecimalValue) throws IOException {
1236 		theEventWriter.write(theChildName, theDecimalValue);
1237 	}
1238 
1239 	private void write(JsonLikeWriter theEventWriter, String theChildName, Integer theValue) throws IOException {
1240 		theEventWriter.write(theChildName, theValue);
1241 	}
1242 
1243 	private void writeCommentsPreAndPost(IBase theNextValue, JsonLikeWriter theEventWriter) throws IOException {
1244 		if (theNextValue.hasFormatComment()) {
1245 			beginArray(theEventWriter, "fhir_comments");
1246 			List<String> pre = theNextValue.getFormatCommentsPre();
1247 			if (pre.isEmpty() == false) {
1248 				for (String next : pre) {
1249 					theEventWriter.write(next);
1250 				}
1251 			}
1252 			List<String> post = theNextValue.getFormatCommentsPost();
1253 			if (post.isEmpty() == false) {
1254 				for (String next : post) {
1255 					theEventWriter.write(next);
1256 				}
1257 			}
1258 			theEventWriter.endArray();
1259 		}
1260 	}
1261 
1262 	private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
1263 															List<HeldExtension> modifierExtensions) throws IOException {
1264 		if (extensions.isEmpty() == false) {
1265 			beginArray(theEventWriter, "extension");
1266 			for (HeldExtension next : extensions) {
1267 				next.write(resDef, theResource, theEventWriter);
1268 			}
1269 			theEventWriter.endArray();
1270 		}
1271 		if (modifierExtensions.isEmpty() == false) {
1272 			beginArray(theEventWriter, "modifierExtension");
1273 			for (HeldExtension next : modifierExtensions) {
1274 				next.write(resDef, theResource, theEventWriter);
1275 			}
1276 			theEventWriter.endArray();
1277 		}
1278 	}
1279 
1280 	private void writeOptionalTagWithTextNode(JsonLikeWriter theEventWriter, String theElementName, IPrimitiveDatatype<?> thePrimitive) throws IOException {
1281 		if (thePrimitive == null) {
1282 			return;
1283 		}
1284 		String str = thePrimitive.getValueAsString();
1285 		writeOptionalTagWithTextNode(theEventWriter, theElementName, str);
1286 	}
1287 
1288 	private void writeOptionalTagWithTextNode(JsonLikeWriter theEventWriter, String theElementName, String theValue) throws IOException {
1289 		if (StringUtils.isNotBlank(theValue)) {
1290 			write(theEventWriter, theElementName, theValue);
1291 		}
1292 	}
1293 
1294 	public static Gson newGson() {
1295 		Gson gson = new GsonBuilder().disableHtmlEscaping().create();
1296 		return gson;
1297 	}
1298 
1299 	private static void write(JsonLikeWriter theWriter, String theName, String theValue) throws IOException {
1300 		theWriter.write(theName, theValue);
1301 	}
1302 
1303 	private class HeldExtension implements Comparable<HeldExtension> {
1304 
1305 		private CompositeChildElement myChildElem;
1306 		private RuntimeChildDeclaredExtensionDefinition myDef;
1307 		private boolean myModifier;
1308 		private IBaseExtension<?, ?> myUndeclaredExtension;
1309 		private IBase myValue;
1310 		private CompositeChildElement myParent;
1311 
1312 		public HeldExtension(IBaseExtension<?, ?> theUndeclaredExtension, boolean theModifier, CompositeChildElement theChildElem, CompositeChildElement theParent) {
1313 			assert theUndeclaredExtension != null;
1314 			myUndeclaredExtension = theUndeclaredExtension;
1315 			myModifier = theModifier;
1316 			myChildElem = theChildElem;
1317 			myParent = theParent;
1318 		}
1319 
1320 		public HeldExtension(RuntimeChildDeclaredExtensionDefinition theDef, IBase theValue, CompositeChildElement theChildElem) {
1321 			assert theDef != null;
1322 			assert theValue != null;
1323 			myDef = theDef;
1324 			myValue = theValue;
1325 			myChildElem = theChildElem;
1326 		}
1327 
1328 		@Override
1329 		public int compareTo(HeldExtension theArg0) {
1330 			String url1 = myDef != null ? myDef.getExtensionUrl() : myUndeclaredExtension.getUrl();
1331 			String url2 = theArg0.myDef != null ? theArg0.myDef.getExtensionUrl() : theArg0.myUndeclaredExtension.getUrl();
1332 			url1 = defaultString(getExtensionUrl(url1));
1333 			url2 = defaultString(getExtensionUrl(url2));
1334 			return url1.compareTo(url2);
1335 		}
1336 
1337 		private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName) throws IOException {
1338 			if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
1339 				final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
1340 				final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
1341 				// Undeclared extensions
1342 				extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null);
1343 				// Declared extensions
1344 				if (def != null) {
1345 					extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent);
1346 				}
1347 				boolean haveContent = false;
1348 				if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) {
1349 					haveContent = true;
1350 				}
1351 				if (haveContent) {
1352 					beginObject(theEventWriter, '_' + childName);
1353 					writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
1354 					theEventWriter.endObject();
1355 				}
1356 			}
1357 		}
1358 
1359 		public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter) throws IOException {
1360 			if (myUndeclaredExtension != null) {
1361 				writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension);
1362 			} else {
1363 				theEventWriter.beginObject();
1364 
1365 				writeCommentsPreAndPost(myValue, theEventWriter);
1366 
1367 				JsonParser.write(theEventWriter, "url", getExtensionUrl(myDef.getExtensionUrl()));
1368 
1369 				/*
1370 				 * This makes sure that even if the extension contains a reference to a contained
1371 				 * resource which has a HAPI-assigned ID we'll still encode that ID.
1372 				 *
1373 				 * See #327
1374 				 */
1375 				List<? extends IBase> preProcessedValue = preProcessValues(myDef, theResource, Collections.singletonList(myValue), myChildElem);
1376 
1377 				// // Check for undeclared extensions on the declared extension
1378 				// // (grrrrrr....)
1379 				// if (myValue instanceof ISupportsUndeclaredExtensions) {
1380 				// ISupportsUndeclaredExtensions value = (ISupportsUndeclaredExtensions)myValue;
1381 				// List<ExtensionDt> exts = value.getUndeclaredExtensions();
1382 				// if (exts.size() > 0) {
1383 				// ArrayList<IBase> newValueList = new ArrayList<IBase>();
1384 				// newValueList.addAll(preProcessedValue);
1385 				// newValueList.addAll(exts);
1386 				// preProcessedValue = newValueList;
1387 				// }
1388 				// }
1389 
1390 				myValue = preProcessedValue.get(0);
1391 
1392 				BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
1393 				if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
1394 					extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null);
1395 				} else {
1396 					String childName = myDef.getChildNameByDatatype(myValue.getClass());
1397 					encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, false, myParent, false);
1398 					managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName);
1399 				}
1400 
1401 				theEventWriter.endObject();
1402 			}
1403 		}
1404 
1405 		private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBaseExtension<?, ?> ext) throws IOException {
1406 			IBase value = ext.getValue();
1407 			final String extensionUrl = getExtensionUrl(ext.getUrl());
1408 
1409 			theEventWriter.beginObject();
1410 
1411 			writeCommentsPreAndPost(myUndeclaredExtension, theEventWriter);
1412 
1413 			String elementId = getCompositeElementId(ext);
1414 			if (isNotBlank(elementId)) {
1415 				JsonParser.write(theEventWriter, "id", getCompositeElementId(ext));
1416 			}
1417 
1418 			JsonParser.write(theEventWriter, "url", extensionUrl);
1419 
1420 			boolean noValue = value == null || value.isEmpty();
1421 			if (noValue && ext.getExtension().isEmpty()) {
1422 				ourLog.debug("Extension with URL[{}] has no value", extensionUrl);
1423 			} else if (noValue) {
1424 
1425 				if (myModifier) {
1426 					beginArray(theEventWriter, "modifierExtension");
1427 				} else {
1428 					beginArray(theEventWriter, "extension");
1429 				}
1430 
1431 				for (Object next : ext.getExtension()) {
1432 					writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next);
1433 				}
1434 				theEventWriter.endArray();
1435 			} else {
1436 
1437 				/*
1438 				 * Pre-process value - This is called in case the value is a reference
1439 				 * since we might modify the text
1440 				 */
1441 				value = JsonParser.super.preProcessValues(myDef, theResource, Collections.singletonList(value), myChildElem).get(0);
1442 
1443 				RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
1444 				String childName = extDef.getChildNameByDatatype(value.getClass());
1445 				if (childName == null) {
1446 					childName = "value" + WordUtils.capitalize(myContext.getElementDefinition(value.getClass()).getName());
1447 				}
1448 				BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
1449 				if (childDef == null) {
1450 					throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
1451 				}
1452 				encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, false, myParent, false);
1453 				managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName);
1454 			}
1455 
1456 			// theEventWriter.name(myUndeclaredExtension.get);
1457 
1458 			theEventWriter.endObject();
1459 		}
1460 	}
1461 }