Support JSON-like resource structures (#469)

* rebuild on 2.1 code and add more test coverage

* additional test coverage
This commit is contained in:
Bill Denton 2016-10-19 07:40:38 -07:00 committed by James Agnew
parent 79e14798cd
commit 913fd32c2b
13 changed files with 2008 additions and 215 deletions

View File

@ -0,0 +1,101 @@
package ca.uhn.fhir.parser;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.parser.json.JsonLikeStructure;
import ca.uhn.fhir.parser.json.JsonLikeWriter;
/**
* An extension to the parser interface that is implemented by parsers that understand a generalized form of
* JSON data. This generalized form uses Map-like, List-like, and scalar elements to construct resources.
* <p>
* Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or
* every message being parsed/encoded.
* </p>
*/
public interface IJsonLikeParser extends IParser {
void encodeBundleToJsonLikeWriter(Bundle theBundle, JsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException;
void encodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException;
void encodeTagListToJsonLikeWriter(TagList theTagList, JsonLikeWriter theJsonLikeWriter) throws IOException;
/**
* Parse a DSTU1 style Atom Bundle. Note that as of DSTU2, Bundle is a resource so you should use
* {@link #parseResource(Class, JsonLikeStructure)} with the Bundle class found in the
* <code>ca.uhn.hapi.fhir.model.[version].resource</code> package instead.
*/
<T extends IBaseResource> Bundle parseBundle(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure);
/**
* Parse a DSTU1 style Atom Bundle. Note that as of DSTU2, Bundle is a resource so you should use
* {@link #parseResource(Class, JsonLikeStructure)} with the Bundle class found in the
* <code>ca.uhn.hapi.fhir.model.[version].resource</code> package instead.
*/
Bundle parseBundle(JsonLikeStructure theJsonLikeStructure) throws DataFormatException;
/**
* Parses a resource from a JSON-like data structure
*
* @param theResourceType
* The resource type to use. This can be used to explicitly specify a class which extends a built-in type
* (e.g. a custom type extending the default Patient class)
* @param theJsonLikeStructure
* The JSON-like structure to parse
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
<T extends IBaseResource> T parseResource(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure) throws DataFormatException;
/**
* Parses a resource from a JSON-like data structure
*
* @param theJsonLikeStructure
* The JSON-like structure to parse
* @return A parsed resource. Note that the returned object will be an instance of {@link IResource} or
* {@link IAnyResource} depending on the specific FhirContext which created this parser.
* @throws DataFormatException
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
IBaseResource parseResource(JsonLikeStructure theJsonLikeStructure) throws DataFormatException;
/**
* Parses a tag list from a JSON-like data structure
*
* @param theJsonLikeStructure
* The JSON-like structure to parse
* @return A parsed tag list
*/
TagList parseTagList(JsonLikeStructure theJsonLikeStructure);
}

View File

@ -0,0 +1,389 @@
package ca.uhn.fhir.parser.json;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.PushbackReader;
import java.io.Reader;
import java.io.Writer;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import ca.uhn.fhir.parser.DataFormatException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
public class GsonStructure implements JsonLikeStructure {
private enum ROOT_TYPE {OBJECT, ARRAY};
private ROOT_TYPE rootType = null;
private JsonElement nativeRoot = null;
private JsonLikeValue jsonLikeRoot = null;
private GsonWriter jsonLikeWriter = null;
public GsonStructure() {
super();
}
public GsonStructure (JsonObject json) {
super();
setNativeObject(json);
}
public GsonStructure (JsonArray json) {
super();
setNativeArray(json);
}
public void setNativeObject (JsonObject json) {
this.rootType = ROOT_TYPE.OBJECT;
this.nativeRoot = json;
}
public void setNativeArray (JsonArray json) {
this.rootType = ROOT_TYPE.ARRAY;
this.nativeRoot = json;
}
@Override
public JsonLikeStructure getInstance() {
return new GsonStructure();
}
@Override
public void load(Reader theReader) throws DataFormatException {
this.load(theReader, false);
}
@Override
public void load(Reader theReader, boolean allowArray) throws DataFormatException {
PushbackReader pbr = new PushbackReader(theReader);
int nextInt;
try {
while(true) {
nextInt = pbr.read();
if (nextInt == -1) {
throw new DataFormatException("Did not find any content to parse");
}
if (nextInt == '{') {
pbr.unread(nextInt);
break;
}
if (Character.isWhitespace(nextInt)) {
continue;
}
if (allowArray) {
if (nextInt == '[') {
pbr.unread(nextInt);
break;
}
throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{' or '[')");
}
throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')");
}
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
if (nextInt == '{') {
JsonObject root = gson.fromJson(pbr, JsonObject.class);
setNativeObject(root);
} else
if (nextInt == '[') {
JsonArray root = gson.fromJson(pbr, JsonArray.class);
setNativeArray(root);
}
} catch (JsonSyntaxException e) {
if (e.getMessage().startsWith("Unexpected char 39")) {
throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e);
}
throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e);
} catch (Exception e) {
throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e);
}
}
@Override
public JsonLikeWriter getJsonLikeWriter (Writer writer) {
if (null == jsonLikeWriter) {
jsonLikeWriter = new GsonWriter(writer);
}
return jsonLikeWriter;
}
@Override
public JsonLikeWriter getJsonLikeWriter () {
if (null == jsonLikeWriter) {
jsonLikeWriter = new GsonWriter();
}
return jsonLikeWriter;
}
@Override
public JsonLikeObject getRootObject() throws DataFormatException {
if (rootType == ROOT_TYPE.OBJECT) {
if (null == jsonLikeRoot) {
jsonLikeRoot = new GsonJsonObject((JsonObject)nativeRoot);
}
return jsonLikeRoot.getAsObject();
}
throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'.");
}
@Override
public JsonLikeArray getRootArray() throws DataFormatException {
if (rootType == ROOT_TYPE.ARRAY) {
if (null == jsonLikeRoot) {
jsonLikeRoot = new GsonJsonArray((JsonArray)nativeRoot);
}
return jsonLikeRoot.getAsArray();
}
throw new DataFormatException("Content must be a valid JSON Array. It must start with '['.");
}
private static class GsonJsonObject extends JsonLikeObject {
private JsonObject nativeObject;
private Set<String> keySet = null;
private Map<String,JsonLikeValue> jsonLikeMap = new LinkedHashMap<String,JsonLikeValue>();
public GsonJsonObject (JsonObject json) {
this.nativeObject = json;
}
@Override
public Object getValue() {
return null;
}
@Override
public Set<String> keySet() {
if (null == keySet) {
Set<Entry<String, JsonElement>> entrySet = nativeObject.entrySet();
keySet = new EntryOrderedSet<String>(entrySet.size());
for (Entry<String,?> entry : entrySet) {
keySet.add(entry.getKey());
}
}
return keySet;
}
@Override
public JsonLikeValue get(String key) {
JsonLikeValue result = null;
if (jsonLikeMap.containsKey(key)) {
result = jsonLikeMap.get(key);
} else {
JsonElement child = nativeObject.get(key);
if (child != null) {
result = new GsonJsonValue(child);
}
jsonLikeMap.put(key, result);
}
return result;
}
}
private static class GsonJsonArray extends JsonLikeArray {
private JsonArray nativeArray;
private Map<Integer,JsonLikeValue> jsonLikeMap = new LinkedHashMap<Integer,JsonLikeValue>();
public GsonJsonArray (JsonArray json) {
this.nativeArray = json;
}
@Override
public Object getValue() {
return null;
}
@Override
public int size() {
return nativeArray.size();
}
@Override
public JsonLikeValue get(int index) {
Integer key = Integer.valueOf(index);
JsonLikeValue result = null;
if (jsonLikeMap.containsKey(key)) {
result = jsonLikeMap.get(key);
} else {
JsonElement child = nativeArray.get(index);
if (child != null) {
result = new GsonJsonValue(child);
}
jsonLikeMap.put(key, result);
}
return result;
}
}
private static class GsonJsonValue extends JsonLikeValue {
private JsonElement nativeValue;
private JsonLikeObject jsonLikeObject = null;
private JsonLikeArray jsonLikeArray = null;
public GsonJsonValue (JsonElement json) {
this.nativeValue = json;
}
@Override
public Object getValue() {
if (nativeValue != null && nativeValue.isJsonPrimitive()) {
if (((JsonPrimitive)nativeValue).isNumber()) {
return nativeValue.getAsNumber();
}
if (((JsonPrimitive)nativeValue).isBoolean()) {
return Boolean.valueOf(nativeValue.getAsBoolean());
}
return nativeValue.getAsString();
}
return null;
}
@Override
public ValueType getJsonType() {
if (null == nativeValue || nativeValue.isJsonNull()) {
return ValueType.NULL;
}
if (nativeValue.isJsonObject()) {
return ValueType.OBJECT;
}
if (nativeValue.isJsonArray()) {
return ValueType.ARRAY;
}
if (nativeValue.isJsonPrimitive()) {
return ValueType.SCALAR;
}
return null;
}
@Override
public ScalarType getDataType() {
if (nativeValue != null && nativeValue.isJsonPrimitive()) {
if (((JsonPrimitive)nativeValue).isNumber()) {
return ScalarType.NUMBER;
}
if (((JsonPrimitive)nativeValue).isString()) {
return ScalarType.STRING;
}
if (((JsonPrimitive)nativeValue).isBoolean()) {
return ScalarType.BOOLEAN;
}
}
return null;
}
@Override
public JsonLikeArray getAsArray() {
if (nativeValue != null && nativeValue.isJsonArray()) {
if (null == jsonLikeArray) {
jsonLikeArray = new GsonJsonArray((JsonArray)nativeValue);
}
}
return jsonLikeArray;
}
@Override
public JsonLikeObject getAsObject() {
if (nativeValue != null && nativeValue.isJsonObject()) {
if (null == jsonLikeObject) {
jsonLikeObject = new GsonJsonObject((JsonObject)nativeValue);
}
}
return jsonLikeObject;
}
@Override
public Number getAsNumber() {
return nativeValue != null ? nativeValue.getAsNumber() : null;
}
@Override
public String getAsString() {
return nativeValue != null ? nativeValue.getAsString() : null;
}
@Override
public boolean getAsBoolean() {
if (nativeValue != null && nativeValue.isJsonPrimitive() && ((JsonPrimitive)nativeValue).isBoolean()) {
return nativeValue.getAsBoolean();
}
return super.getAsBoolean();
}
}
private static class EntryOrderedSet<T> extends AbstractSet<T> {
private transient ArrayList<T> data = null;
public EntryOrderedSet (int initialCapacity) {
data = new ArrayList<T>(initialCapacity);
}
@SuppressWarnings("unused")
public EntryOrderedSet () {
data = new ArrayList<T>();
}
@Override
public int size() {
return data.size();
}
@Override
public boolean contains(Object o) {
return data.contains(o);
}
@SuppressWarnings("unused") // not really.. just not here
public T get(int index) {
return data.get(index);
}
@Override
public boolean add(T element) {
if (data.contains(element)) {
return false;
}
return data.add(element);
}
@Override
public boolean remove(Object o) {
return data.remove(o);
}
@Override
public void clear() {
data.clear();
}
@Override
public Iterator<T> iterator() {
return data.iterator();
}
}
}

View File

@ -0,0 +1,263 @@
package ca.uhn.fhir.parser.json;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Stack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.stream.JsonWriter;
public class GsonWriter extends JsonLikeWriter {
private static final Logger log = LoggerFactory.getLogger(GsonWriter.class);
private JsonWriter eventWriter;
private enum BlockType {
NONE, OBJECT, ARRAY
}
private BlockType blockType = BlockType.NONE;
private Stack<BlockType> blockStack = new Stack<BlockType>();
public GsonWriter () {
super();
}
public GsonWriter (Writer writer) {
setWriter(writer);
}
@Override
public JsonLikeWriter init() throws IOException {
eventWriter = new JsonWriter(getWriter());
eventWriter.setSerializeNulls(true);
if (isPrettyPrint()) {
eventWriter.setIndent(" ");
}
blockType = BlockType.NONE;
blockStack.clear();
return this;
}
@Override
public JsonLikeWriter flush() throws IOException {
if (blockType != BlockType.NONE) {
log.error("JsonLikeStreamWriter.flush() called but JSON document is not finished");
}
eventWriter.flush();
getWriter().flush();
return this;
}
@Override
public void close() throws IOException {
eventWriter.close();
getWriter().close();
}
@Override
public JsonLikeWriter beginObject() throws IOException {
blockStack.push(blockType);
blockType = BlockType.OBJECT;
eventWriter.beginObject();
return this;
}
@Override
public JsonLikeWriter beginArray() throws IOException {
blockStack.push(blockType);
blockType = BlockType.ARRAY;
eventWriter.beginArray();
return this;
}
@Override
public JsonLikeWriter beginObject(String name) throws IOException {
blockStack.push(blockType);
blockType = BlockType.OBJECT;
eventWriter.name(name);
eventWriter.beginObject();
return this;
}
@Override
public JsonLikeWriter beginArray(String name) throws IOException {
blockStack.push(blockType);
blockType = BlockType.ARRAY;
eventWriter.name(name);
eventWriter.beginArray();
return this;
}
@Override
public JsonLikeWriter write(String value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(BigInteger value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(BigDecimal value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(long value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(double value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(Boolean value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(boolean value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter writeNull() throws IOException {
eventWriter.nullValue();
return this;
}
@Override
public JsonLikeWriter write(String name, String value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, BigInteger value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, BigDecimal value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, long value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, double value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, Boolean value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, boolean value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter writeNull(String name) throws IOException {
eventWriter.name(name);
eventWriter.nullValue();
return this;
}
@Override
public JsonLikeWriter endObject() throws IOException {
if (blockType == BlockType.NONE) {
log.error("JsonLikeStreamWriter.endObject(); called with no active JSON document");
} else {
if (blockType != BlockType.OBJECT) {
log.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)");
eventWriter.endArray();
} else {
eventWriter.endObject();
}
blockType = blockStack.pop();
}
return this;
}
@Override
public JsonLikeWriter endArray() throws IOException {
if (blockType == BlockType.NONE) {
log.error("JsonLikeStreamWriter.endArray(); called with no active JSON document");
} else {
if (blockType != BlockType.ARRAY) {
log.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)");
eventWriter.endObject();
} else {
eventWriter.endArray();
}
blockType = blockStack.pop();
}
return this;
}
@Override
public JsonLikeWriter endBlock() throws IOException {
if (blockType == BlockType.NONE) {
log.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document");
} else {
if (blockType == BlockType.ARRAY) {
eventWriter.endArray();
} else {
eventWriter.endObject();
}
blockType = blockStack.pop();
}
return this;
}
}

View File

@ -0,0 +1,53 @@
package ca.uhn.fhir.parser.json;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public abstract class JsonLikeArray extends JsonLikeValue {
@Override
public ValueType getJsonType() {
return ValueType.ARRAY;
}
@Override
public ScalarType getDataType() {
return null;
}
@Override
public boolean isArray() {
return true;
}
@Override
public JsonLikeArray getAsArray() {
return this;
}
@Override
public String getAsString() {
return null;
}
public abstract int size ();
public abstract JsonLikeValue get (int index);
}

View File

@ -0,0 +1,72 @@
package ca.uhn.fhir.parser.json;
import java.util.Set;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public abstract class JsonLikeObject extends JsonLikeValue {
@Override
public ValueType getJsonType() {
return ValueType.OBJECT;
}
@Override
public ScalarType getDataType() {
return null;
}
@Override
public boolean isObject() {
return true;
}
@Override
public JsonLikeObject getAsObject() {
return this;
}
@Override
public String getAsString() {
return null;
}
public abstract Set<String> keySet ();
public abstract JsonLikeValue get (String key);
public String getString (String key) {
JsonLikeValue value = this.get(key);
if (null == value) {
throw new NullPointerException("Json object missing element named \""+key+"\"");
}
return value.getAsString();
}
public String getString (String key, String defaultValue) {
String result = defaultValue;
JsonLikeValue value = this.get(key);
if (value != null) {
result = value.getAsString();
}
return result;
}
}

View File

@ -0,0 +1,52 @@
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.parser.json;
import java.io.Reader;
import java.io.Writer;
import ca.uhn.fhir.parser.DataFormatException;
/**
* This interface is the generic representation of any sort of data
* structure that looks and smells like JSON. These data structures
* can be abstractly viewed as a <code.Map</code> or <code>List</code>
* whose members are other Maps, Lists, or scalars (Strings, Numbers, Boolean)
*
* @author Bill.Denton
*/
public interface JsonLikeStructure {
public JsonLikeStructure getInstance();
/**
* Parse the JSON document into the Json-like structure
* so that it can be navigated.
*
* @param theReader a <code>Reader</code> that will
* process the JSON input stream
* @throws DataFormatException when invalid JSON is received
*/
public void load (Reader theReader) throws DataFormatException;
public void load (Reader theReader, boolean allowArray) throws DataFormatException;
public JsonLikeObject getRootObject () throws DataFormatException;
public JsonLikeArray getRootArray () throws DataFormatException;
public JsonLikeWriter getJsonLikeWriter ();
public JsonLikeWriter getJsonLikeWriter (Writer writer);
}

View File

@ -0,0 +1,228 @@
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.parser.json;
/**
* This is the generalization of anything that is a "value"
* element in a JSON structure. This could be a JSON object,
* a JSON array, a scalar value (number, string, boolean),
* or a null.
*
*/
public abstract class JsonLikeValue {
public enum ValueType {
ARRAY, OBJECT, SCALAR, NULL
};
public enum ScalarType {
NUMBER, STRING, BOOLEAN
}
public abstract ValueType getJsonType ();
public abstract ScalarType getDataType ();
public abstract Object getValue ();
public boolean isArray () {
return this.getJsonType() == ValueType.ARRAY;
}
public boolean isObject () {
return this.getJsonType() == ValueType.OBJECT;
}
public boolean isScalar () {
return this.getJsonType() == ValueType.SCALAR;
}
public boolean isString () {
return this.getJsonType() == ValueType.SCALAR && this.getDataType() == ScalarType.STRING;
}
public boolean isNumber () {
return this.getJsonType() == ValueType.SCALAR && this.getDataType() == ScalarType.NUMBER;
}
public boolean isNull () {
return this.getJsonType() == ValueType.NULL;
}
public JsonLikeArray getAsArray () {
return null;
}
public JsonLikeObject getAsObject () {
return null;
}
public String getAsString () {
return this.toString();
}
public Number getAsNumber () {
return this.isNumber() ? (Number)this.getValue() : null;
}
public boolean getAsBoolean () {
return !isNull();
}
public static JsonLikeArray asArray (JsonLikeValue element) {
if (element != null) {
return element.getAsArray();
}
return null;
}
public static JsonLikeObject asObject (JsonLikeValue element) {
if (element != null) {
return element.getAsObject();
}
return null;
}
public static String asString (JsonLikeValue element) {
if (element != null) {
return element.getAsString();
}
return null;
}
public static boolean asBoolean (JsonLikeValue element) {
if (element != null) {
return element.getAsBoolean();
}
return false;
}
public static final JsonLikeValue NULL = new JsonLikeValue() {
@Override
public ValueType getJsonType() {
return ValueType.NULL;
}
@Override
public ScalarType getDataType() {
return null;
}
@Override
public Object getValue() {
return null;
}
@Override
public boolean equals (Object obj) {
if (this == obj){
return true;
}
if (obj instanceof JsonLikeValue) {
return getJsonType().equals(((JsonLikeValue)obj).getJsonType());
}
return false;
}
@Override
public int hashCode() {
return "null".hashCode();
}
@Override
public String toString() {
return "null";
}
};
public static final JsonLikeValue TRUE = new JsonLikeValue() {
@Override
public ValueType getJsonType() {
return ValueType.SCALAR;
}
@Override
public ScalarType getDataType() {
return ScalarType.BOOLEAN;
}
@Override
public Object getValue() {
return Boolean.TRUE;
}
@Override
public boolean equals(Object obj) {
if (this == obj){
return true;
}
if (obj instanceof JsonLikeValue) {
return getJsonType().equals(((JsonLikeValue)obj).getJsonType())
&& getDataType().equals(((JsonLikeValue)obj).getDataType())
&& toString().equals(((JsonLikeValue)obj).toString());
}
return false;
}
@Override
public int hashCode() {
return "true".hashCode();
}
@Override
public String toString() {
return "true";
}
};
public static final JsonLikeValue FALSE = new JsonLikeValue() {
@Override
public ValueType getJsonType() {
return ValueType.SCALAR;
}
@Override
public ScalarType getDataType() {
return ScalarType.BOOLEAN;
}
@Override
public Object getValue() {
return Boolean.FALSE;
}
@Override
public boolean equals(Object obj) {
if (this == obj){
return true;
}
if (obj instanceof JsonLikeValue) {
return getJsonType().equals(((JsonLikeValue)obj).getJsonType())
&& getDataType().equals(((JsonLikeValue)obj).getDataType())
&& toString().equals(((JsonLikeValue)obj).toString());
}
return false;
}
@Override
public int hashCode() {
return "false".hashCode();
}
@Override
public String toString() {
return "false";
}
};
}

View File

@ -0,0 +1,83 @@
package ca.uhn.fhir.parser.json;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public abstract class JsonLikeWriter {
private boolean prettyPrint;
private Writer writer;
public void setPrettyPrint (boolean tf) {
prettyPrint = tf;
}
public boolean isPrettyPrint () {
return prettyPrint;
}
public void setWriter (Writer writer) {
this.writer = writer;
}
public Writer getWriter () {
return writer;
}
public abstract JsonLikeWriter init () throws IOException;
public abstract JsonLikeWriter flush () throws IOException;
public abstract void close () throws IOException;
public abstract JsonLikeWriter beginObject () throws IOException;
public abstract JsonLikeWriter beginArray () throws IOException;
public abstract JsonLikeWriter beginObject (String name) throws IOException;
public abstract JsonLikeWriter beginArray (String name) throws IOException;
public abstract JsonLikeWriter write (String value) throws IOException;
public abstract JsonLikeWriter write (BigInteger value) throws IOException;
public abstract JsonLikeWriter write (BigDecimal value) throws IOException;
public abstract JsonLikeWriter write (long value) throws IOException;
public abstract JsonLikeWriter write (double value) throws IOException;
public abstract JsonLikeWriter write (Boolean value) throws IOException;
public abstract JsonLikeWriter write (boolean value) throws IOException;
public abstract JsonLikeWriter writeNull () throws IOException;
public abstract JsonLikeWriter write (String name, String value) throws IOException;
public abstract JsonLikeWriter write (String name, BigInteger value) throws IOException;
public abstract JsonLikeWriter write (String name, BigDecimal value) throws IOException;
public abstract JsonLikeWriter write (String name, long value) throws IOException;
public abstract JsonLikeWriter write (String name, double value) throws IOException;
public abstract JsonLikeWriter write (String name, Boolean value) throws IOException;
public abstract JsonLikeWriter write (String name, boolean value) throws IOException;
public abstract JsonLikeWriter writeNull (String name) throws IOException;
public abstract JsonLikeWriter endObject () throws IOException;
public abstract JsonLikeWriter endArray () throws IOException;
public abstract JsonLikeWriter endBlock () throws IOException;
public JsonLikeWriter() {
super();
}
}

View File

@ -0,0 +1,145 @@
package ca.uhn.fhir.parser.json;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.StringReader;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
public class JsonLikeStructureTest {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeStructureTest.class);
private static final String TEST_STRUCTURELOADING_DATA =
"{" +
" \"resourceType\":\"Organization\"," +
" \"id\":\"11111\"," +
" \"meta\":{" +
" \"lastUpdated\":\"3900-09-20T10:10:10.000-07:00\"" +
" }," +
" \"identifier\":[" +
" {" +
" \"value\":\"15250\"" +
" }" +
" ]," +
" \"type\":{" +
" \"coding\":[" +
" {" +
" \"system\":\"http://test\"," +
" \"code\":\"ins\"," +
" \"display\":\"General Ledger System\"," +
" \"userSelected\":false" +
" }" +
" ]" +
" }," +
" \"name\":\"Acme Investments\"" +
"}";
@Test
public void testStructureLoading() {
StringReader reader = new StringReader(TEST_STRUCTURELOADING_DATA);
JsonLikeStructure jsonStructure = new GsonStructure();
jsonStructure.load(reader);
JsonLikeObject rootObject = jsonStructure.getRootObject();
assertNotNull(rootObject);
assertEquals(JsonLikeValue.ValueType.OBJECT, rootObject.getJsonType());
}
private static final String TEST_JSONTYPES_DATA =
"{" +
" \"scalar-string\":\"A scalar string\"," +
" \"scalar-number\":11111," +
" \"scalar-boolean\":true," +
" \"null-value\":null," +
" \"object-value\":{" +
" \"lastUpdated\":\"3900-09-20T10:10:10.000-07:00\"," +
" \"deleted\":\"3909-09-20T10:10:10.000-07:00\"" +
" }," +
" \"array-value\":[" +
" 12345," +
" {" +
" \"value\":\"15250\"" +
" }" +
" ]" +
"}";
@Test
public void testJsonAndDataTypes() {
StringReader reader = new StringReader(TEST_JSONTYPES_DATA);
JsonLikeStructure jsonStructure = new GsonStructure();
jsonStructure.load(reader);
JsonLikeObject rootObject = jsonStructure.getRootObject();
assertNotNull(rootObject);
JsonLikeValue value = rootObject.get("object-value");
assertNotNull(value);
assertEquals(JsonLikeValue.ValueType.OBJECT, value.getJsonType());
assertEquals(true, value.isObject());
assertEquals(false, value.isArray());
assertEquals(false, value.isScalar());
assertEquals(false, value.isNull());
JsonLikeObject obj = value.getAsObject();
assertNotNull(obj);
assertEquals(JsonLikeValue.ValueType.OBJECT, obj.getJsonType());
assertEquals(true, obj.isObject());
assertEquals(false, obj.isArray());
assertEquals(false, obj.isScalar());
assertEquals(false, obj.isNull());
value = rootObject.get("array-value");
assertNotNull(value);
assertEquals(JsonLikeValue.ValueType.ARRAY, value.getJsonType());
assertEquals(false, value.isObject());
assertEquals(true, value.isArray());
assertEquals(false, value.isScalar());
assertEquals(false, value.isNull());
JsonLikeArray array = value.getAsArray();
assertNotNull(array);
assertEquals(JsonLikeValue.ValueType.ARRAY, array.getJsonType());
assertEquals(false, array.isObject());
assertEquals(true, array.isArray());
assertEquals(false, array.isScalar());
assertEquals(false, array.isNull());
value = rootObject.get("null-value");
assertNotNull(value);
assertEquals(JsonLikeValue.ValueType.NULL, value.getJsonType());
assertEquals(false, value.isObject());
assertEquals(false, value.isArray());
assertEquals(false, value.isScalar());
assertEquals(true, value.isNull());
value = rootObject.get("scalar-string");
assertNotNull(value);
assertEquals(JsonLikeValue.ValueType.SCALAR, value.getJsonType());
assertEquals(false, value.isObject());
assertEquals(false, value.isArray());
assertEquals(true, value.isScalar());
assertEquals(false, value.isNull());
assertEquals(JsonLikeValue.ScalarType.STRING, value.getDataType());
assertEquals(value.getAsString(), "A scalar string");
value = rootObject.get("scalar-number");
assertNotNull(value);
assertEquals(JsonLikeValue.ValueType.SCALAR, value.getJsonType());
assertEquals(JsonLikeValue.ScalarType.NUMBER, value.getDataType());
assertEquals(value.getAsString(), "11111");
value = rootObject.get("scalar-boolean");
assertNotNull(value);
assertEquals(JsonLikeValue.ValueType.SCALAR, value.getJsonType());
assertEquals(JsonLikeValue.ScalarType.BOOLEAN, value.getDataType());
assertEquals(value.getAsString(), "true");
}
}

View File

@ -0,0 +1,226 @@
package ca.uhn.fhir.parser.jsonlike;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IJsonLikeParser;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.JsonParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.parser.XmlParser;
import ca.uhn.fhir.parser.json.GsonStructure;
import ca.uhn.fhir.parser.json.JsonLikeStructure;
import ca.uhn.fhir.parser.json.JsonLikeWriter;
import ca.uhn.fhir.util.TestUtil;
import net.sf.json.JSON;
import net.sf.json.JSONSerializer;
public class JsonLikeParserTest {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserTest.class);
@Test
public void testJsonLikeSimpleBundleEncode() throws InterruptedException, IOException {
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/atom-document-large.xml"), Charset.forName("UTF-8"));
Bundle obs = ourCtx.newXmlParser().parseBundle(xmlString);
IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser().setPrettyPrint(true);
StringWriter stringWriter = new StringWriter();
JsonLikeStructure jsonLikeStructure = new GsonStructure();
JsonLikeWriter jsonLikeWriter = jsonLikeStructure.getJsonLikeWriter(stringWriter);
jsonLikeParser.encodeBundleToJsonLikeWriter(obs, jsonLikeWriter);
String encoded = stringWriter.toString();
ourLog.info(encoded);
}
@Test
public void testJsonLikeSimpleResourceEncode() throws IOException {
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.xml"), Charset.forName("UTF-8"));
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
Patient obs = parser.parseResource(Patient.class, xmlString);
IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser();
StringWriter stringWriter = new StringWriter();
JsonLikeStructure jsonLikeStructure = new GsonStructure();
JsonLikeWriter jsonLikeWriter = jsonLikeStructure.getJsonLikeWriter(stringWriter);
jsonLikeParser.encodeResourceToJsonLikeWriter(obs, jsonLikeWriter);
String encoded = stringWriter.toString();
ourLog.info(encoded);
String jsonString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.json"), Charset.forName("UTF-8"));
JSON expected = JSONSerializer.toJSON(jsonString);
JSON actual = JSONSerializer.toJSON(encoded.trim());
// The encoded escapes quote marks using XML escaping instead of JSON escaping, which is probably nicer anyhow...
String exp = expected.toString().replace("\\\"Jim\\\"", "&quot;Jim&quot;");
String act = actual.toString();
ourLog.info("Expected: {}", exp);
ourLog.info("Actual : {}", act);
assertEquals("\nExpected: " + exp + "\nActual : " + act, exp, act);
}
@Test
public void testJsonLikeSimpleResourceParse() throws DataFormatException, IOException {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/example-patient-general.json"));
IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser().setPrettyPrint(true);
StringReader reader = new StringReader(msg);
JsonLikeStructure jsonLikeStructure = new GsonStructure();
jsonLikeStructure.load(reader);
Patient res = jsonLikeParser.parseResource(Patient.class, jsonLikeStructure);
assertEquals(2, res.getUndeclaredExtensions().size());
assertEquals(1, res.getUndeclaredModifierExtensions().size());
}
@Test
public void testJsonLikeParseBundle() throws DataFormatException, IOException {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/atom-document-large.json"));
IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser().setPrettyPrint(true);
StringReader reader = new StringReader(msg);
JsonLikeStructure jsonLikeStructure = new GsonStructure();
jsonLikeStructure.load(reader);
Bundle bundle = jsonLikeParser.parseBundle(jsonLikeStructure);
assertEquals(1, bundle.getCategories().size());
assertEquals("http://scheme", bundle.getCategories().get(0).getScheme());
assertEquals("http://term", bundle.getCategories().get(0).getTerm());
assertEquals("label", bundle.getCategories().get(0).getLabel());
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(bundle);
ourLog.info(encoded);
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/_search?_format=application/json+fhir&search-id=46d5f0e7-9240-4d4f-9f51-f8ac975c65&search-sort=_id",
bundle.getLinkSelf().getValue());
assertEquals("urn:uuid:0b754ff9-03cf-4322-a119-15019af8a3", bundle.getBundleId().getValue());
BundleEntry entry = bundle.getEntries().get(0);
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101", entry.getId().getValue());
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101/_history/1", entry.getLinkSelf().getValue());
assertEquals("2014-03-10T11:55:59Z", entry.getUpdated().getValueAsString());
DiagnosticReport res = (DiagnosticReport) entry.getResource();
assertEquals("Complete Blood Count", res.getName().getText().getValue());
assertThat(entry.getSummary().getValueAsString(), containsString("CBC Report for Wile"));
}
@Test
public void testJsonLikeTagList() throws IOException {
//@formatter:off
String tagListStr = "{\n" +
" \"resourceType\" : \"TagList\", " +
" \"category\" : [" +
" { " +
" \"term\" : \"term0\", " +
" \"label\" : \"label0\", " +
" \"scheme\" : \"scheme0\" " +
" }," +
" { " +
" \"term\" : \"term1\", " +
" \"label\" : \"label1\", " +
" \"scheme\" : null " +
" }," +
" { " +
" \"term\" : \"term2\", " +
" \"label\" : \"label2\" " +
" }" +
" ] " +
"}";
//@formatter:on
IJsonLikeParser jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser().setPrettyPrint(true);
StringReader reader = new StringReader(tagListStr);
JsonLikeStructure jsonLikeStructure = new GsonStructure();
jsonLikeStructure.load(reader);
TagList tagList = jsonLikeParser.parseTagList(jsonLikeStructure);
assertEquals(3, tagList.size());
assertEquals("term0", tagList.get(0).getTerm());
assertEquals("label0", tagList.get(0).getLabel());
assertEquals("scheme0", tagList.get(0).getScheme());
assertEquals("term1", tagList.get(1).getTerm());
assertEquals("label1", tagList.get(1).getLabel());
assertEquals(null, tagList.get(1).getScheme());
assertEquals("term2", tagList.get(2).getTerm());
assertEquals("label2", tagList.get(2).getLabel());
assertEquals(null, tagList.get(2).getScheme());
/*
* Encode
*/
//@formatter:off
String expected = "{" +
"\"resourceType\":\"TagList\"," +
"\"category\":[" +
"{" +
"\"term\":\"term0\"," +
"\"label\":\"label0\"," +
"\"scheme\":\"scheme0\"" +
"}," +
"{" +
"\"term\":\"term1\"," +
"\"label\":\"label1\"" +
"}," +
"{" +
"\"term\":\"term2\"," +
"\"label\":\"label2\"" +
"}" +
"]" +
"}";
//@formatter:on
jsonLikeParser = (IJsonLikeParser)ourCtx.newJsonParser();
StringWriter stringWriter = new StringWriter();
jsonLikeStructure = new GsonStructure();
JsonLikeWriter jsonLikeWriter = jsonLikeStructure.getJsonLikeWriter(stringWriter);
jsonLikeParser.encodeTagListToJsonLikeWriter(tagList, jsonLikeWriter);
String encoded = stringWriter.toString();
assertEquals(expected, encoded);
}
@BeforeClass
public static void beforeClass() {
ourCtx = FhirContext.forDstu1();
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -1688,7 +1688,7 @@ public class JsonParserDstu2Test {
ourCtx.newJsonParser().parseResource(Conformance.class, input);
fail();
} catch (DataFormatException e) {
assertEquals("Syntax error parsing JSON FHIR structure: Expected ARRAY at element 'modifierExtension', found 'JsonObject'", e.getMessage());
assertEquals("Syntax error parsing JSON FHIR structure: Expected ARRAY at element 'modifierExtension', found 'OBJECT'", e.getMessage());
}
}

View File

@ -0,0 +1,44 @@
package ca.uhn.fhir.parser.jsonlike;
import java.io.StringReader;
import org.apache.commons.io.IOUtils;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IJsonLikeParser;
import ca.uhn.fhir.parser.json.GsonStructure;
import ca.uhn.fhir.parser.json.JsonLikeStructure;
import ca.uhn.fhir.util.TestUtil;
public class JsonLikeParserDstu2Test {
private static FhirContext ourCtx = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonLikeParserDstu2Test.class);
/**
* Test for #146
*/
@Test
public void testJsonLikeParseAndEncodeBundleFromXmlToJson() throws Exception {
String content = IOUtils.toString(JsonLikeParserDstu2Test.class.getResourceAsStream("/bundle-example2.xml"));
ca.uhn.fhir.model.dstu2.resource.Bundle parsed = ourCtx.newXmlParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, content);
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed);
ourLog.info(encoded);
JsonLikeStructure jsonLikeStructure = new GsonStructure();
jsonLikeStructure.load(new StringReader(encoded));
IJsonLikeParser jsonLikeparser = (IJsonLikeParser)ourCtx.newJsonParser();
ca.uhn.fhir.model.dstu2.resource.Bundle bundle = jsonLikeparser.parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, jsonLikeStructure);
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}