Correctly encode JSON composite elements with extensions

This commit is contained in:
James Agnew 2014-08-05 14:57:53 -04:00
parent f29349f537
commit aef7ea5e9f
5 changed files with 141 additions and 92 deletions

View File

@ -23,6 +23,11 @@
FHIR Tester UI now correctly sends UTF-8 charset in responses so that message payloads containing
non US-ASCII characters will correctly display in the browser
</action>
<action type="fix">
JSON parser was incorrectly encoding extensions on composite elements outside the element itself
(as is done correctly for non-composite elements) instead of inside of them. Thanks to David Hay of
Orion for reporting this!
</action>
</release>
<release version="0.5" date="2014-Jul-30">
<action type="add">

View File

@ -30,6 +30,11 @@ public abstract class BaseRuntimeChildDefinition {
public abstract IAccessor getAccessor();
@Override
public String toString() {
return getClass().getSimpleName()+"[" + getElementName() + "]";
}
public abstract BaseRuntimeElementDefinition<?> getChildByName(String theName);
public abstract BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IElement> theType);

View File

@ -56,6 +56,11 @@ public abstract class BaseRuntimeElementDefinition<T extends IElement> {
myImplementingClass = theImplementingClass;
}
@Override
public String toString() {
return getClass().getSimpleName()+"[" + getName() + "]";
}
public void addExtension(RuntimeChildDeclaredExtensionDefinition theExtension) {
if (theExtension == null) {
throw new NullPointerException();

View File

@ -227,11 +227,16 @@ public class JsonParser extends BaseParser implements IParser {
eventWriter.flush();
}
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef, String theChildName) throws IOException {
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef,
String theChildName) throws IOException {
switch (theChildDef.getChildType()) {
case PRIMITIVE_DATATYPE: {
IPrimitiveDatatype<?> value = (IPrimitiveDatatype<?>) theValue;
if (isBlank(value.getValueAsString())) {
break;
}
if (value instanceof IntegerDt) {
if (theChildName != null) {
theWriter.write(theChildName, ((IntegerDt) value).getValue());
@ -333,7 +338,8 @@ public class JsonParser extends BaseParser implements IParser {
}
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter, List<? extends BaseRuntimeChildDefinition> theChildren) throws IOException {
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter,
List<? extends BaseRuntimeChildDefinition> theChildren) throws IOException {
for (BaseRuntimeChildDefinition nextChild : theChildren) {
if (nextChild instanceof RuntimeChildNarrativeDefinition) {
INarrativeGenerator gen = myContext.getNarrativeGenerator();
@ -372,14 +378,16 @@ public class JsonParser extends BaseParser implements IParser {
if (childDef == null) {
super.throwExceptionForUnknownChildType(nextChild, type);
}
boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
if (nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild;
if (extDef.isModifier()) {
addToHeldExtensions(valueIdx, modifierExtensions, extDef, nextValue);
} else {
addToHeldExtensions(valueIdx, extensions, extDef, nextValue);
}
// Don't encode extensions
// RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild;
// if (extDef.isModifier()) {
// addToHeldExtensions(valueIdx, modifierExtensions, extDef, nextValue);
// } else {
// addToHeldExtensions(valueIdx, extensions, extDef, nextValue);
// }
} else {
if (currentChildName == null || !currentChildName.equals(childName)) {
@ -398,7 +406,7 @@ public class JsonParser extends BaseParser implements IParser {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null);
}
if (nextValue instanceof ISupportsUndeclaredExtensions) {
if (nextValue instanceof ISupportsUndeclaredExtensions && primitive) {
List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions();
addToHeldExtensions(valueIdx, ext, extensions);
@ -416,9 +424,6 @@ public class JsonParser extends BaseParser implements IParser {
}
if (extensions.size() > 0 || modifierExtensions.size() > 0) {
// Ignore extensions if we're encoding a resource, since they
// are handled one level up
if (currentChildName != null) {
theEventWriter.writeStartArray('_' + currentChildName);
for (int i = 0; i < valueIdx; i++) {
@ -440,37 +445,20 @@ public class JsonParser extends BaseParser implements IParser {
}
}
// if (extensions.size() > 0) {
//
// theEventWriter.name(extType);
// theEventWriter.beginArray();
// for (ArrayList<HeldExtension> next : extensions) {
// if (next == null || next.isEmpty()) {
// theEventWriter.nullValue();
// } else {
// theEventWriter.beginArray();
// // next.write(theEventWriter);
// theEventWriter.endArray();
// }
// }
// for (int i = extensions.size(); i < valueIdx; i++) {
// theEventWriter.nullValue();
// }
// theEventWriter.endArray();
// }
theEventWriter.writeEnd();
}
}
}
}
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef) throws IOException, DataFormatException {
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter,
BaseRuntimeElementCompositeDefinition<?> resDef) throws IOException, DataFormatException {
extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, resDef, theResDef, theResource);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions());
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren());
}
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, boolean theIsSubElementWithinResource) throws IOException {
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull,
boolean theIsSubElementWithinResource) throws IOException {
if (!theIsSubElementWithinResource) {
super.containResourcesForEncoding(theResource);
}
@ -493,7 +481,6 @@ public class JsonParser extends BaseParser implements IParser {
theEventWriter.write("contentType", bin.getContentType());
theEventWriter.write("content", bin.getContentAsBase64());
} else {
extractAndWriteExtensionsAsDirectChild(theResource, theEventWriter, resDef, theResDef, theResource);
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, resDef);
}
theEventWriter.writeEnd();
@ -541,10 +528,10 @@ public class JsonParser extends BaseParser implements IParser {
}
/**
* This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object
* called _name): resource extensions, and extension extensions
* This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object called _name): resource extensions, and extension extensions
*/
private void extractAndWriteExtensionsAsDirectChild(IElement theElement, JsonGenerator theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef, IResource theResource) throws IOException {
private void extractAndWriteExtensionsAsDirectChild(IElement theElement, JsonGenerator theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef,
IResource theResource) throws IOException {
List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
@ -895,7 +882,8 @@ public class JsonParser extends BaseParser implements IParser {
}
}
private void writeExtensionsAsDirectChild(IResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions) throws IOException {
private void writeExtensionsAsDirectChild(IResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
List<HeldExtension> modifierExtensions) throws IOException {
if (extensions.isEmpty() == false) {
theEventWriter.writeStartArray("extension");
for (HeldExtension next : extensions) {

View File

@ -83,6 +83,67 @@ public class JsonParserTest {
}
@Test
public void testEncodeExtensionInCompositeElement() {
Conformance c = new Conformance();
c.addRest().getSecurity().addUndeclaredExtension(false, "http://foo", new StringDt("AAA"));
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(c);
ourLog.info(encoded);
encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(c);
ourLog.info(encoded);
assertEquals(encoded, "{\"resourceType\":\"Conformance\",\"rest\":[{\"security\":{\"extension\":[{\"url\":\"http://foo\",\"valueString\":\"AAA\"}]}}]}");
}
@Test
public void testEncodeExtensionInPrimitiveElement() {
Conformance c = new Conformance();
c.getAcceptUnknown().addUndeclaredExtension(false, "http://foo", new StringDt("AAA"));
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(c);
ourLog.info(encoded);
encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(c);
ourLog.info(encoded);
assertEquals(encoded, "{\"resourceType\":\"Conformance\",\"_acceptUnknown\":[{\"extension\":[{\"url\":\"http://foo\",\"valueString\":\"AAA\"}]}]}");
// Now with a value
ourLog.info("---------------");
c = new Conformance();
c.getAcceptUnknown().setValue(true);
c.getAcceptUnknown().addUndeclaredExtension(false, "http://foo", new StringDt("AAA"));
encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(c);
ourLog.info(encoded);
encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(c);
ourLog.info(encoded);
assertEquals(encoded, "{\"resourceType\":\"Conformance\",\"acceptUnknown\":true,\"_acceptUnknown\":[{\"extension\":[{\"url\":\"http://foo\",\"valueString\":\"AAA\"}]}]}");
}
@Test
public void testEncodeExtensionInResourceElement() {
Conformance c = new Conformance();
// c.addRest().getSecurity().addUndeclaredExtension(false, "http://foo", new StringDt("AAA"));
c.addUndeclaredExtension(false, "http://foo", new StringDt("AAA"));
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(c);
ourLog.info(encoded);
encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(c);
ourLog.info(encoded);
assertEquals(encoded, "{\"resourceType\":\"Conformance\",\"extension\":[{\"url\":\"http://foo\",\"valueString\":\"AAA\"}]}");
}
@Test
public void testEncodeBinaryResource() {
@ -388,6 +449,17 @@ public class JsonParserTest {
}
@Test
public void testEncodeExtensionOnEmptyElement() throws Exception {
ValueSet valueSet = new ValueSet();
valueSet.addTelecom().addUndeclaredExtension(false, "http://foo", new StringDt("AAA"));
String encoded = ourCtx.newJsonParser().encodeResourceToString(valueSet);
assertThat(encoded, containsString("\"telecom\":[{\"extension\":[{\"url\":\"http://foo\",\"valueString\":\"AAA\"}]}"));
}
@Test
@ -402,11 +474,11 @@ public class JsonParserTest {
code.setDisplay("someDisplay");
code.addUndeclaredExtension(false, "urn:alt", new StringDt("alt name"));
String encoded = new FhirContext().newJsonParser().encodeResourceToString(valueSet);
String encoded = ourCtx.newJsonParser().encodeResourceToString(valueSet);
ourLog.info(encoded);
assertThat(encoded, not(containsString("123456")));
assertThat(encoded, containsString("\"define\":{\"concept\":[{\"code\":\"someCode\",\"display\":\"someDisplay\"}],\"_concept\":[{\"extension\":[{\"url\":\"urn:alt\",\"valueString\":\"alt name\"}]}]}"));
assertEquals("{\"resourceType\":\"ValueSet\",\"define\":{\"concept\":[{\"extension\":[{\"url\":\"urn:alt\",\"valueString\":\"alt name\"}],\"code\":\"someCode\",\"display\":\"someDisplay\"}]}}", encoded);
}
@ -500,31 +572,7 @@ public class JsonParserTest {
given.addUndeclaredExtension(ext2);
String enc = new FhirContext().newJsonParser().encodeResourceToString(patient);
ourLog.info(enc);
//@formatter:off
assertThat(enc, containsString(("{" +
" \"resourceType\":\"Patient\"," +
" \"name\":[" +
" {" +
" \"family\":[" +
" \"Shmoe\"" +
" ]," +
" \"given\":[" +
" \"Joe\"" +
" ]" +
" }" +
" ]," +
" \"_name\":[" +
" {" +
" \"extension\":[" +
" {" +
" \"url\":\"http://examples.com#givenext\"," +
" \"valueString\":\"Hello\"" +
" }" +
" ]" +
" }" +
" ]" +
"}").replaceAll(" +", "")));
//@formatter:on
assertEquals("{\"resourceType\":\"Patient\",\"name\":[{\"extension\":[{\"url\":\"http://examples.com#givenext\",\"valueString\":\"Hello\"}],\"family\":[\"Shmoe\"],\"given\":[\"Joe\"]}]}", enc);
IParser newJsonParser = new FhirContext().newJsonParser();
StringReader reader = new StringReader(enc);
@ -837,9 +885,7 @@ public class JsonParserTest {
ExtensionDt undeclaredExtension = undeclaredExtensions.get(0);
assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl().getValue());
fhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(obs, new OutputStreamWriter(System.out));
IParser jsonParser = fhirCtx.newJsonParser();
IParser jsonParser = fhirCtx.newJsonParser().setPrettyPrint(true);
String encoded = jsonParser.encodeResourceToString(obs);
ourLog.info(encoded);