diff --git a/src/Benchmarks/SerializationBenchmarks.cs b/src/Benchmarks/SerializationBenchmarks.cs index 23917aec13..e32f855f5b 100644 --- a/src/Benchmarks/SerializationBenchmarks.cs +++ b/src/Benchmarks/SerializationBenchmarks.cs @@ -13,9 +13,8 @@ namespace Firely.Sdk.Benchmarks public class SerializationBenchmarks { internal Patient Patient; - JsonSerializerOptions Options; - BaseFhirXmlPocoSerializer XmlSerializer; - + private JsonSerializerOptions _options; + private FhirXmlSerializer _xmlSerializer; [GlobalSetup] public void BenchmarkSetup() @@ -25,20 +24,20 @@ public void BenchmarkSetup() // For now, deserialize with the existing deserializer, until we have completed // the dynamicserializer too. Patient = FhirJsonNode.Parse(data).ToPoco(); - Options = new JsonSerializerOptions().ForFhir(); - XmlSerializer = new FhirXmlPocoSerializer(); + _options = new JsonSerializerOptions().ForFhir(); + _xmlSerializer = new FhirXmlSerializer(); } [Benchmark] public string JsonDictionarySerializer() { - return JsonSerializer.Serialize(Patient, Options); + return JsonSerializer.Serialize(Patient, _options); } [Benchmark] public string XmlDictionarySerializer() { - return SerializationUtil.WriteXmlToString(Patient, (o, w) => XmlSerializer.Serialize(o, w)); + return SerializationUtil.WriteXmlToString(w => _xmlSerializer.Serialize(Patient, w)); } [Benchmark] diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index 48190dcf42..d728f2b101 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -43,6 +43,27 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0001 + T:Hl7.Fhir.Serialization.BaseFhirSerializer + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.CommonFhirJsonSerializer + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.CommonFhirXmlSerializer + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0001 T:Hl7.Fhir.Serialization.DefaultModelFactory @@ -50,6 +71,55 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0001 + T:Hl7.Fhir.Serialization.FhirJsonConverter`1 + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.FhirJsonSerializationSettings + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.FhirXmlSerializationSettings + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0001 + T:Hl7.Fhir.Serialization.SerializerSettings + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 F:Hl7.Fhir.ElementModel.ScopedNode.Parent @@ -365,6 +435,55 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer.get_Settings + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirJsonPocoSerializer.get_Release + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirJsonPocoSerializer.get_Settings + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Serialization.BaseFhirJsonPocoSerializer.Serialize(System.Collections.Generic.IReadOnlyDictionary{System.String,System.Object},System.Text.Json.Utf8JsonWriter) @@ -379,6 +498,20 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoSerializer.get_Release + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Serialization.BaseFhirXmlPocoSerializer.Serialize(System.Collections.Generic.IReadOnlyDictionary{System.String,System.Object},System.Xml.XmlWriter,Hl7.Fhir.Serialization.SerializationFilter) @@ -395,28 +528,217 @@ CP0002 - M:Hl7.Fhir.Serialization.FhirJsonConverter`1.#ctor(Hl7.Fhir.Introspection.ModelInspector) + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJObject(Hl7.Fhir.ElementModel.ISourceNode,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJObject(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJson(Hl7.Fhir.ElementModel.ISourceNode,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) lib/net8.0/Hl7.Fhir.Base.dll lib/net8.0/Hl7.Fhir.Base.dll true CP0002 - M:Hl7.Fhir.Serialization.FhirJsonConverter`1.#ctor(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,Hl7.Fhir.Serialization.BaseFhirJsonPocoSerializer) + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJson(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) lib/net8.0/Hl7.Fhir.Base.dll lib/net8.0/Hl7.Fhir.Base.dll true CP0002 - M:Hl7.Fhir.Serialization.FhirJsonConverter`1.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings,System.Predicate{Hl7.Fhir.Utility.CodedException}) + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJsonAsync(Hl7.Fhir.ElementModel.ISourceNode,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) lib/net8.0/Hl7.Fhir.Base.dll lib/net8.0/Hl7.Fhir.Base.dll true CP0002 - M:Hl7.Fhir.Serialization.FhirJsonConverter`1.#ctor(System.Reflection.Assembly) + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJsonAsync(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJsonBytes(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.ToJsonBytesAsync(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.WriteTo(Hl7.Fhir.ElementModel.ISourceNode,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.WriteTo(Hl7.Fhir.ElementModel.ITypedElement,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.WriteToAsync(Hl7.Fhir.ElementModel.ISourceNode,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonBuilderExtensions.WriteToAsync(Hl7.Fhir.ElementModel.ITypedElement,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonConverterFactory.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonConverterFactory.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirSerializationEngineFactory.BackwardsCompatible(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirSerializationEngineFactory.Custom(Hl7.Fhir.Introspection.ModelInspector,System.Predicate{Hl7.Fhir.Utility.CodedException},Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirSerializationEngineFactory.Ostrich(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirSerializationEngineFactory.Recoverable(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirSerializationEngineFactory.Strict(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXDocument(Hl7.Fhir.ElementModel.ISourceNode,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXDocument(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXml(Hl7.Fhir.ElementModel.ISourceNode,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXml(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXmlAsync(Hl7.Fhir.ElementModel.ISourceNode,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXmlAsync(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXmlBytes(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.ToXmlBytesAsync(Hl7.Fhir.ElementModel.ITypedElement,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.WriteTo(Hl7.Fhir.ElementModel.ISourceNode,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.WriteTo(Hl7.Fhir.ElementModel.ITypedElement,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.WriteToAsync(Hl7.Fhir.ElementModel.ISourceNode,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlBuilderExtensions.WriteToAsync(Hl7.Fhir.ElementModel.ITypedElement,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) lib/net8.0/Hl7.Fhir.Base.dll lib/net8.0/Hl7.Fhir.Base.dll true @@ -435,6 +757,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Serialization.SerializationFilter.ForElements(System.String[]) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Specification.Snapshot.SnapshotGeneratorExtensions.RemoveAllNonInheritableExtensions(Hl7.Fhir.Model.Element) @@ -456,6 +785,83 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.JObjectFromJsonText(System.String) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.JObjectFromJsonTextAsync(System.String) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteJsonToBytes(System.Action{Newtonsoft.Json.JsonWriter}) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteJsonToBytesAsync(System.Func{Newtonsoft.Json.JsonWriter,System.Threading.Tasks.Task}) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteJsonToString(System.Action{Newtonsoft.Json.JsonWriter},System.Boolean,System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteJsonToStringAsync(System.Func{Newtonsoft.Json.JsonWriter,System.Threading.Tasks.Task},System.Boolean,System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteXmlToBytes(System.Action{System.Xml.XmlWriter}) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteXmlToBytesAsync(System.Func{System.Xml.XmlWriter,System.Threading.Tasks.Task}) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteXmlToString(System.Action{System.Xml.XmlWriter},System.Boolean,System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteXmlToString``1(``0,System.Action{``0,System.Xml.XmlWriter},System.Boolean,System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Utility.SerializationUtil.WriteXmlToStringAsync(System.Func{System.Xml.XmlWriter,System.Threading.Tasks.Task},System.Boolean,System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.FhirPath.CompiledExpression.BeginInvoke(Hl7.Fhir.ElementModel.ITypedElement,Hl7.FhirPath.EvaluationContext,System.AsyncCallback,System.Object) @@ -1093,13 +1499,6 @@ lib/net8.0/Hl7.Fhir.Base.dll true - - CP0009 - T:Hl7.Fhir.Serialization.FhirJsonConverter`1 - lib/net8.0/Hl7.Fhir.Base.dll - lib/net8.0/Hl7.Fhir.Base.dll - true - PKV006 .NETStandard,Version=v2.0 diff --git a/src/Hl7.Fhir.Base/ElementModel/PocoBuilderExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/PocoBuilderExtensions.cs index 390b51d396..d50f3700f1 100644 --- a/src/Hl7.Fhir.Base/ElementModel/PocoBuilderExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/PocoBuilderExtensions.cs @@ -30,12 +30,7 @@ public static T ToPoco(this ITypedElement element, ModelInspector inspector, public static Base ToPoco(this ITypedElement element, ModelInspector inspector, PocoBuilderSettings settings = null) => new NewPocoBuilder(inspector, settings ?? new PocoBuilderSettings()).BuildFrom(element); - public static ISourceNode ToSourceNode(this Base @base, ModelInspector inspector, string rootName = null) - { - var node = @base.ToElementNode(rootName); - ((IAnnotatable)node).AddAnnotation(inspector); - - return node; - } + public static ISourceNode ToSourceNode(this Base @base, ModelInspector inspector, string rootName = null) => + @base.ToPocoNode(inspector, rootName); } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/PocoNodeOrList.cs b/src/Hl7.Fhir.Base/ElementModel/PocoNodeOrList.cs index fdd82d5edb..42bc812cdb 100644 --- a/src/Hl7.Fhir.Base/ElementModel/PocoNodeOrList.cs +++ b/src/Hl7.Fhir.Base/ElementModel/PocoNodeOrList.cs @@ -107,7 +107,6 @@ Element when Poco.TypeName.Contains('.') => "Element", _ => Name }; - [TemporarilyChanged] // Parent should return PocoNode, not PocoNodeOrList. This will be solved in another branch. IElementDefinitionSummary? ITypedElement.Definition { get @@ -143,7 +142,7 @@ IEnumerable IScopedNode.Children(string? name) => name is null ? Children().SelectMany(node => node) : Child(name) ?? []; - [TemporarilyChanged] // will be removed soon + [Obsolete("This is a temporary feature and will be removed before we publish SDK 6.0.")] NodeType IScopedNode.Type => Poco switch { Bundle => NodeType.Bundle | NodeType.Resource, @@ -219,7 +218,7 @@ IEnumerable ISourceNode.Children(string? name) private AnnotationList? _annotations; - private AnnotationList Annotations => LazyInitializer.EnsureInitialized(ref _annotations, () => []); + private AnnotationList Annotations => LazyInitializer.EnsureInitialized(ref _annotations, () => [])!; IEnumerable IAnnotated.Annotations(Type type) { diff --git a/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs index f53349aef7..c031b96cae 100644 --- a/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs @@ -44,24 +44,27 @@ public static ITypedElement ToTypedElementLegacy(this Base @base, ModelInspector /// In the meantime, you can restore the old behaviour with a call to #if NETSTANDARD2_1 [Obsolete("The implementation of this method has changed to use our new model stack. If you want to try the new behaviour, "+ - "either ignore this warning or call ToElementNode(). For reverting to the old behaviour, call .ToTypedElementLegacy()")] + "either ignore this warning or call ToPocoNode(). For reverting to the old behaviour, call .ToTypedElementLegacy()")] #else [Experimental("SDK0001")] #endif - public static ITypedElement ToTypedElement(this Base @base, ModelInspector inspector, string? rootName = null) - { - var node = ToElementNode(@base, rootName); - ((IAnnotatable)node).AddAnnotation(inspector); - - return node; - } + public static ITypedElement ToTypedElement(this Base @base, ModelInspector inspector, string? rootName = null) => + @base.ToPocoNode(inspector, rootName); /// /// Converts a Poco to a new PocoElementNode. /// /// The Poco that should be converted to an . - /// - public static PocoNode ToElementNode(this Base @base, string? rootName = null) => PocoNodeOrList.Root(@base, rootName); + /// An optional that should be used to access metadata about the resource. + /// An optional nome for the node at the root of the tree. + public static PocoNode ToPocoNode(this Base @base, ModelInspector? inspector = null, string? rootName = null) + { + var result = PocoNodeOrList.Root(@base, rootName); + if(inspector is not null) + ((IAnnotatable)result).AddAnnotation(inspector); + + return result; + } /// /// Determines whether the specified ITypedElement is equal to the current ITypedElement. You can discard the order of the elements diff --git a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs index 2d44425f84..40e9df0dd3 100644 --- a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs +++ b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs @@ -41,7 +41,7 @@ public static bool Predicate(this CompiledExpression evaluator, IScopedNode inpu /// public static bool Predicate(this CompiledExpression evaluator, Base input, EvaluationContext ctx) { - var result = evaluator(input.ToElementNode(), ctx).BooleanEval(); + var result = evaluator(input.ToPocoNode(), ctx).BooleanEval(); return result is null || result.Value; } diff --git a/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs b/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs index f7bfb6c0ce..f6d8b68ad4 100644 --- a/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs +++ b/src/Hl7.Fhir.Base/FhirPath/Expressions/EvaluatorVisitor.cs @@ -26,7 +26,9 @@ public EvaluatorVisitor(SymbolTable symbols) } - [TemporarilyChanged] // we should investigate here. We will need a way to handle "object" without a fhir type, and try to infer it from the object + [TemporarilyChanged] + // we should investigate here. We will need a way to handle "object" without a fhir type, + // and try to infer it from the object. In the end we will remove the ToScopedNode. public override Invokee VisitConstant(FP.ConstantExpression expression) { return InvokeeFactory.Return(ElementNode.ForPrimitive(expression.Value).ToScopedNode()); @@ -133,4 +135,4 @@ public static Invokee ToEvaluator(this FP.Expression expr, SymbolTable scope) } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Model/Base.Extensions.cs b/src/Hl7.Fhir.Base/Model/Base.Extensions.cs index 513e2365d4..51874afb98 100644 --- a/src/Hl7.Fhir.Base/Model/Base.Extensions.cs +++ b/src/Hl7.Fhir.Base/Model/Base.Extensions.cs @@ -1,14 +1,18 @@ #nullable enable +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Introspection; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Utility; using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace Hl7.Fhir.Model; public static class BaseExtensions { - [Obsolete("Use GetElementPairs() instead. Note that with GetElementPairs(), the elements are not guaranteed to " + - "be the same type, as they reflect the type in the actual POCO definition.")] + [Obsolete("Use EnumerateElements() instead. Note that with EnumerateElements(), the elements 'div' and 'id' are not FhirStrings, but XHtml and FhirUri respectively.")] public static IEnumerable Children(this Base instance) { foreach (var element in instance.EnumerateElements()) diff --git a/src/Hl7.Fhir.Base/Rest/SearchParams.cs b/src/Hl7.Fhir.Base/Rest/SearchParams.cs index 57082a83d7..52a30b5c64 100644 --- a/src/Hl7.Fhir.Base/Rest/SearchParams.cs +++ b/src/Hl7.Fhir.Base/Rest/SearchParams.cs @@ -31,440 +31,453 @@ POSSIBILITY OF SUCH DAMAGE. */ using Hl7.Fhir.Introspection; +using Hl7.Fhir.Serialization; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; -namespace Hl7.Fhir.Rest +namespace Hl7.Fhir.Rest; + +/// +/// Contains criteria that can be passed to a search operation or conditional update/delete/create +/// +public class SearchParams { /// - /// Contains criteria that can be passed to a search operation or conditional update/delete/create + /// Construct an empty instance. + /// + public SearchParams() + { + // Nothing + } + + /// + /// Construct a new instance initialized with a single name/value. + /// + public SearchParams(string name, string value) : this() => Add(name, value); + + /// + /// List of all the search parameter that have some special meaning. + /// Primarily used to filter to the non-special parameters. + /// Notice that _query, _text, _filter and _content are predefined searches in the standard, + /// but cannot be parsed as regular criteria. So they are included in the RESERVED_PARAMETERS + /// and thus not included in the Parameters property + /// + public static readonly string[] RESERVED_PARAMETERS = new string[] { + SEARCH_PARAM_QUERY, + SEARCH_PARAM_TEXT, + SEARCH_PARAM_CONTENT, + SEARCH_PARAM_COUNT, + SEARCH_PARAM_SORT, + SEARCH_PARAM_FILTER, + SEARCH_PARAM_INCLUDE, + SEARCH_PARAM_REVINCLUDE, + SEARCH_PARAM_SUMMARY, + SEARCH_PARAM_CONTAINED, + SEARCH_PARAM_CONTAINEDTYPE, + SEARCH_PARAM_ELEMENTS + }; + + public const string SEARCH_MODIF_ASCENDING = "asc"; + public const string SEARCH_MODIF_DESCENDING = "desc"; + + public const char SEARCH_CHAINSEPARATOR = '.'; + public const char SEARCH_MODIFIERSEPARATOR = ':'; + + public const string SEARCH_CONTAINED_TRUE = "true"; + public const string SEARCH_CONTAINED_FALSE = "false"; + public const string SEARCH_CONTAINED_BOTH = "both"; + + public const string SEARCH_CONTAINED_TYPE_CONTAINER = "container"; + public const string SEARCH_CONTAINED_TYPE_CONTAINED = "contained"; + + /// + /// Initializes the parameter with the given list of elements. + /// + public SearchParams Select(params string[] elements) + { + Elements.AddRange(elements); + return this; + } + + /// + /// Add a parameter with a given name and value. /// - public class SearchParams + /// The name of the parameter + /// The value of the parameter as a FHIR datatype or Resource + /// this (Parameters), so you can chain AddParameter calls + public SearchParams Add(string name, string value) { - /// - /// Construct an empty instance. - /// - public SearchParams() + if (name == null) throw Error.ArgumentNull(nameof(name)); + if (value == null) throw Error.ArgumentNull(nameof(value)); + + if (name == SEARCH_PARAM_QUERY) Query = nonEmptySingleValue(name, Query, value); + else if (name == SEARCH_PARAM_TEXT) Text = nonEmptySingleValue(name, Text, value); + else if (name == SEARCH_PARAM_CONTENT) Content = nonEmptySingleValue(name, Content, value); + else if (name == SEARCH_PARAM_COUNT) { - // Nothing + if (!Int32.TryParse(value, out int count) || count < 0) throw Error.Format("Invalid {0}: '{1}' is not a non-negative integer".FormatWith(name, value)); + Count = count; } - - /// - /// Construct a new instance initialized with a single name/value. - /// - public SearchParams(string name, string value) : this() => Add(name, value); - - /// - /// List of all the search parameter that have some special meaning. - /// Primarily used to filter to the non-special parameters. - /// Notice that _query, _text, _filter and _content are predefined searches in the standard, - /// but cannot be parsed as regular criteria. So they are included in the RESERVED_PARAMETERS - /// and thus not included in the Parameters property - /// - public static readonly string[] RESERVED_PARAMETERS = new string[] { - SEARCH_PARAM_QUERY, - SEARCH_PARAM_TEXT, - SEARCH_PARAM_CONTENT, - SEARCH_PARAM_COUNT, - SEARCH_PARAM_SORT, - SEARCH_PARAM_FILTER, - SEARCH_PARAM_INCLUDE, - SEARCH_PARAM_REVINCLUDE, - SEARCH_PARAM_SUMMARY, - SEARCH_PARAM_CONTAINED, - SEARCH_PARAM_CONTAINEDTYPE, - SEARCH_PARAM_ELEMENTS - }; - - public const string SEARCH_MODIF_ASCENDING = "asc"; - public const string SEARCH_MODIF_DESCENDING = "desc"; - - public const char SEARCH_CHAINSEPARATOR = '.'; - public const char SEARCH_MODIFIERSEPARATOR = ':'; - - public const string SEARCH_CONTAINED_TRUE = "true"; - public const string SEARCH_CONTAINED_FALSE = "false"; - public const string SEARCH_CONTAINED_BOTH = "both"; - - public const string SEARCH_CONTAINED_TYPE_CONTAINER = "container"; - public const string SEARCH_CONTAINED_TYPE_CONTAINED = "contained"; - - /// - /// Initializes the parameter with the given list of elements. - /// - public SearchParams Select(params string[] elements) + else if (name.StartsWith(SEARCH_PARAM_INCLUDE + SEARCH_MODIFIERSEPARATOR)) { - Elements.AddRange(elements); - return this; + if (Enum.TryParse(name.Substring(SEARCH_PARAM_INCLUDE.Length + 1), ignoreCase: true, out var modifier)) + addNonEmpty(name, Include, (value, modifier)); + else + throw Error.Format($"Invalid include modifier in {name}"); } - - /// - /// Add a parameter with a given name and value. - /// - /// The name of the parameter - /// The value of the parameter as a FHIR datatype or Resource - /// this (Parameters), so you can chain AddParameter calls - public SearchParams Add(string name, string value) + else if (name == SEARCH_PARAM_INCLUDE) addNonEmpty(name, Include, (value, IncludeModifier.None)); + else if (name.StartsWith(SEARCH_PARAM_REVINCLUDE + SEARCH_MODIFIERSEPARATOR)) { - if (name == null) throw Error.ArgumentNull(nameof(name)); - if (value == null) throw Error.ArgumentNull(nameof(value)); - - if (name == SEARCH_PARAM_QUERY) Query = nonEmptySingleValue(name, Query, value); - else if (name == SEARCH_PARAM_TEXT) Text = nonEmptySingleValue(name, Text, value); - else if (name == SEARCH_PARAM_CONTENT) Content = nonEmptySingleValue(name, Content, value); - else if (name == SEARCH_PARAM_COUNT) - { - if (!Int32.TryParse(value, out int count) || count < 0) throw Error.Format("Invalid {0}: '{1}' is not a non-negative integer".FormatWith(name, value)); - Count = count; - } - else if (name.StartsWith(SEARCH_PARAM_INCLUDE + SEARCH_MODIFIERSEPARATOR)) - { - if (Enum.TryParse(name.Substring(SEARCH_PARAM_INCLUDE.Length + 1), ignoreCase: true, out var modifier)) - addNonEmpty(name, Include, (value, modifier)); - else - throw Error.Format($"Invalid include modifier in {name}"); - } - else if (name == SEARCH_PARAM_INCLUDE) addNonEmpty(name, Include, (value, IncludeModifier.None)); - else if (name.StartsWith(SEARCH_PARAM_REVINCLUDE + SEARCH_MODIFIERSEPARATOR)) - { - if (Enum.TryParse(name.Substring(SEARCH_PARAM_REVINCLUDE.Length + 1), ignoreCase: true, out var modifier)) - addNonEmpty(name, RevInclude, (value, modifier)); - else - throw Error.Format($"Invalid revinclude modifier in {name}"); - } - else if (name == SEARCH_PARAM_REVINCLUDE) addNonEmpty(name, RevInclude, (value, IncludeModifier.None)); - else if (name.StartsWith(SEARCH_PARAM_SORT + SEARCH_MODIFIERSEPARATOR)) - throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: encountered DSTU2 (modifier) based sort, please change to newer format"); - else if (name == SEARCH_PARAM_SORT) - { - if (String.IsNullOrEmpty(value)) - throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: value cannot be empty"); - var elements = value.Split(','); - if (elements.Any(String.IsNullOrEmpty)) - throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: must be a list of non-empty element names"); - if (elements.Any(f => f == "-")) - throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: one of the values is just a single '-', an element name must be provided"); - if (!elements.All(f => Char.IsLetter(f[0]) || f[0] == '-' || f[0] == '_')) - throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: must be a list of element names, optionally prefixed with '-'"); - - addNonEmptySort(elements); - } - else if (name == SEARCH_PARAM_SUMMARY) - { - if (Enum.TryParse(value, ignoreCase: true, result: out SummaryType st)) - Summary = st; - else - throw Error.Format("Invalid {0}: '{1}' is not a recognized summary value".FormatWith(name, value)); - } - else if (name == SEARCH_PARAM_FILTER) Filter = nonEmptySingleValue(name, Filter, value); - else if (name == SEARCH_PARAM_CONTAINED) - { - if (SEARCH_CONTAINED_TRUE.Equals(value)) Contained = ContainedSearch.True; - else if (SEARCH_CONTAINED_FALSE.Equals(value)) Contained = ContainedSearch.False; - else if (SEARCH_CONTAINED_BOTH.Equals(value)) Contained = ContainedSearch.Both; - else throw Error.Format("Invalid {0}: '{1}' is not a recognized contained value".FormatWith(name, value)); - } - else if (name == SEARCH_PARAM_CONTAINEDTYPE) - { - if (SEARCH_CONTAINED_TYPE_CONTAINED.Equals(value)) ContainedType = ContainedResult.Contained; - else if (SEARCH_CONTAINED_TYPE_CONTAINER.Equals(value)) ContainedType = ContainedResult.Container; - else throw Error.Format("Invalid {0}: '{1}' is not a recognized containedType value".FormatWith(name, value)); - } - else if (name == SEARCH_PARAM_ELEMENTS) - { - if (String.IsNullOrEmpty(value)) throw Error.Format("Invalid {0} value: it cannot be empty".FormatWith(name)); - Elements.AddRange(value.Split(',')); - } + if (Enum.TryParse(name.Substring(SEARCH_PARAM_REVINCLUDE.Length + 1), ignoreCase: true, out var modifier)) + addNonEmpty(name, RevInclude, (value, modifier)); else - Parameters.Add(Tuple.Create(name, value)); - - return this; + throw Error.Format($"Invalid revinclude modifier in {name}"); } - - private static string nonEmptySingleValue(string paramName, string? currentValue, string newValue) + else if (name == SEARCH_PARAM_REVINCLUDE) addNonEmpty(name, RevInclude, (value, IncludeModifier.None)); + else if (name.StartsWith(SEARCH_PARAM_SORT + SEARCH_MODIFIERSEPARATOR)) + throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: encountered DSTU2 (modifier) based sort, please change to newer format"); + else if (name == SEARCH_PARAM_SORT) { - if (String.IsNullOrEmpty(newValue)) throw Error.Format("Invalid {0} value: it cannot be empty".FormatWith(paramName)); - if (!String.IsNullOrEmpty(currentValue)) throw Error.Format("{0} cannot be specified more than once".FormatWith(paramName)); - return newValue; + if (String.IsNullOrEmpty(value)) + throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: value cannot be empty"); + var elements = value.Split(','); + if (elements.Any(String.IsNullOrEmpty)) + throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: must be a list of non-empty element names"); + if (elements.Any(f => f == "-")) + throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: one of the values is just a single '-', an element name must be provided"); + if (!elements.All(f => Char.IsLetter(f[0]) || f[0] == '-' || f[0] == '_')) + throw Error.Format($"Invalid {SEARCH_PARAM_SORT}: must be a list of element names, optionally prefixed with '-'"); + + addNonEmptySort(elements); } - - private static void addNonEmpty(string paramName, IList<(string path, IncludeModifier modifier)> values, (string path, IncludeModifier modifier) value) + else if (name == SEARCH_PARAM_SUMMARY) + { + if (Enum.TryParse(value, ignoreCase: true, result: out SummaryType st)) + Summary = st; + else + throw Error.Format("Invalid {0}: '{1}' is not a recognized summary value".FormatWith(name, value)); + } + else if (name == SEARCH_PARAM_FILTER) Filter = nonEmptySingleValue(name, Filter, value); + else if (name == SEARCH_PARAM_CONTAINED) + { + if (SEARCH_CONTAINED_TRUE.Equals(value)) Contained = ContainedSearch.True; + else if (SEARCH_CONTAINED_FALSE.Equals(value)) Contained = ContainedSearch.False; + else if (SEARCH_CONTAINED_BOTH.Equals(value)) Contained = ContainedSearch.Both; + else throw Error.Format("Invalid {0}: '{1}' is not a recognized contained value".FormatWith(name, value)); + } + else if (name == SEARCH_PARAM_CONTAINEDTYPE) + { + if (SEARCH_CONTAINED_TYPE_CONTAINED.Equals(value)) ContainedType = ContainedResult.Contained; + else if (SEARCH_CONTAINED_TYPE_CONTAINER.Equals(value)) ContainedType = ContainedResult.Container; + else throw Error.Format("Invalid {0}: '{1}' is not a recognized containedType value".FormatWith(name, value)); + } + else if (name == SEARCH_PARAM_ELEMENTS) { - if (String.IsNullOrEmpty(value.path)) throw Error.Format("Invalid {0} value: it cannot be empty".FormatWith(paramName)); - values.Add(value); + if (String.IsNullOrEmpty(value)) throw Error.Format("Invalid {0} value: it cannot be empty".FormatWith(name)); + Elements.AddRange(value.Split(',')); } + else + Parameters.Add(Tuple.Create(name, value)); + + return this; + } + + private static string nonEmptySingleValue(string paramName, string? currentValue, string newValue) + { + if (String.IsNullOrEmpty(newValue)) throw Error.Format("Invalid {0} value: it cannot be empty".FormatWith(paramName)); + if (!String.IsNullOrEmpty(currentValue)) throw Error.Format("{0} cannot be specified more than once".FormatWith(paramName)); + return newValue; + } + + private static void addNonEmpty(string paramName, IList<(string path, IncludeModifier modifier)> values, (string path, IncludeModifier modifier) value) + { + if (String.IsNullOrEmpty(value.path)) throw Error.Format("Invalid {0} value: it cannot be empty".FormatWith(paramName)); + values.Add(value); + } - private void addNonEmptySort(string[] elements) + private void addNonEmptySort(string[] elements) + { + foreach (var e in elements) { - foreach (var e in elements) - { - var newTuple = e[0] == '-' ? (e.Substring(1), SortOrder.Descending) : - (e, SortOrder.Ascending); - Sort.Add(newTuple); - } + var newTuple = e[0] == '-' ? (e.Substring(1), SortOrder.Descending) : + (e, SortOrder.Ascending); + Sort.Add(newTuple); } + } + + /// + /// The 'regular' parameters. The parameters that have no special meaning. + /// + public IList> Parameters { get; } = []; + + public const string SEARCH_PARAM_QUERY = "_query"; + + /// + /// Gets or sets the special _query search parameter which asks the server to run a + /// specific named query instead of the standard FHIR search. + /// + [NotMapped] + [IgnoreDataMember] + public string? Query { get; set; } + + public const string SEARCH_PARAM_TEXT = "_text"; + + /// + /// Gets or sets the special _text search parameter which which search on the narrative of the resource. + /// + [NotMapped] + [IgnoreDataMember] + public string? Text { get; set; } + + + public const string SEARCH_PARAM_CONTENT = "_content"; + + /// + /// Gets or sets the special _text search parameter which which search on the entire content of the resource. + /// + [NotMapped] + [IgnoreDataMember] + public string? Content { get; set; } - /// - /// The 'regular' parameters. The parameters that have no special meaning. - /// - public IList> Parameters { get; } = []; - - public const string SEARCH_PARAM_QUERY = "_query"; - - /// - /// Gets or sets the special _query search parameter which asks the server to run a - /// specific named query instead of the standard FHIR search. - /// - [NotMapped] - [IgnoreDataMember] - public string? Query { get; set; } - public const string SEARCH_PARAM_TEXT = "_text"; + public const string SEARCH_PARAM_COUNT = "_count"; - /// - /// Gets or sets the special _text search parameter which which search on the narrative of the resource. - /// - [NotMapped] - [IgnoreDataMember] - public string? Text { get; set; } + /// + /// Gets or sets the special _count search parameter, which limits the number + /// of mathes returned per page in the pages search result + /// + /// The number of resources returned from the search may exceed this + /// parameter, since additional _included resources for the matches are returned + /// as well + [NotMapped] + [IgnoreDataMember] + public int? Count { get; set; } + public const string SEARCH_PARAM_SUMMARY = "_summary"; - public const string SEARCH_PARAM_CONTENT = "_content"; + /// + /// Gets or sets the special _summary search parameter. If set to true, + /// the server will not return all elements in each matching resource, but just + /// the most important ones. + /// + [NotMapped] + [IgnoreDataMember] + public SummaryType? Summary { get; set; } - /// - /// Gets or sets the special _text search parameter which which search on the entire content of the resource. - /// - [NotMapped] - [IgnoreDataMember] - public string? Content { get; set; } + public const string SEARCH_PARAM_FILTER = "_filter"; + /// + /// Gets or sets the special _filter search parameter to supply an advanced query expression + /// + [NotMapped] + [IgnoreDataMember] + public string? Filter { get; set; } - public const string SEARCH_PARAM_COUNT = "_count"; + /// + /// "_sort" + /// + public const string SEARCH_PARAM_SORT = "_sort"; - /// - /// Gets or sets the special _count search parameter, which limits the number - /// of mathes returned per page in the pages search result - /// - /// The number of resources returned from the search may exceed this - /// parameter, since additional _included resources for the matches are returned - /// as well - [NotMapped] - [IgnoreDataMember] - public int? Count { get; set; } + /// + /// Gets or sets the _sort parameter, to modify the sort order of the search result. + /// Uses a tuple (name, sortorder). + /// + [NotMapped] + [IgnoreDataMember] + public IList<(string, SortOrder)> Sort { get; } = []; - public const string SEARCH_PARAM_SUMMARY = "_summary"; - /// - /// Gets or sets the special _summary search parameter. If set to true, - /// the server will not return all elements in each matching resource, but just - /// the most important ones. - /// - [NotMapped] - [IgnoreDataMember] - public SummaryType? Summary { get; set; } + public const string SEARCH_PARAM_INCLUDE = "_include"; - public const string SEARCH_PARAM_FILTER = "_filter"; + /// + /// Returns a modifiable collection of _include parameters. These are used to include + /// resources in the search result that the matched resources refer to. + /// + [NotMapped] + [IgnoreDataMember] + public IList<(string, IncludeModifier)> Include { get; } = []; - /// - /// Gets or sets the special _filter search parameter to supply an advanced query expression - /// - [NotMapped] - [IgnoreDataMember] - public string? Filter { get; set; } - - /// - /// "_sort" - /// - public const string SEARCH_PARAM_SORT = "_sort"; - - /// - /// Gets or sets the _sort parameter, to modify the sort order of the search result. - /// Uses a tuple (name, sortorder). - /// - [NotMapped] - [IgnoreDataMember] - public IList<(string, SortOrder)> Sort { get; } = []; + public const string SEARCH_PARAM_REVINCLUDE = "_revinclude"; + [NotMapped] + [IgnoreDataMember] + public IList<(string, IncludeModifier)> RevInclude { get; } = []; - public const string SEARCH_PARAM_INCLUDE = "_include"; - /// - /// Returns a modifiable collection of _include parameters. These are used to include - /// resources in the search result that the matched resources refer to. - /// - [NotMapped] - [IgnoreDataMember] - public IList<(string, IncludeModifier)> Include { get; } = []; + public const string SEARCH_PARAM_CONTAINED = "_contained"; - public const string SEARCH_PARAM_REVINCLUDE = "_revinclude"; + [NotMapped] + [IgnoreDataMember] + public ContainedSearch? Contained { get; private set; } - [NotMapped] - [IgnoreDataMember] - public IList<(string, IncludeModifier)> RevInclude { get; } = []; + public const string SEARCH_PARAM_CONTAINEDTYPE = "_containedType"; - public const string SEARCH_PARAM_CONTAINED = "_contained"; - - [NotMapped] - [IgnoreDataMember] - public ContainedSearch? Contained { get; private set; } + [NotMapped] + [IgnoreDataMember] + public ContainedResult? ContainedType { get; private set; } - public const string SEARCH_PARAM_CONTAINEDTYPE = "_containedType"; - - [NotMapped] - [IgnoreDataMember] - public ContainedResult? ContainedType { get; private set; } - + public const string SEARCH_PARAM_ELEMENTS = "_elements"; - public const string SEARCH_PARAM_ELEMENTS = "_elements"; + public List Elements { get; } = []; - public List Elements { get; } = []; + /// + /// Take a list of key/value pairs and turn them into a new instance. + /// + public static SearchParams FromUriParamList(IEnumerable> parameters) + { + var result = new SearchParams(); - /// - /// Take a list of key/value pairs and turn them into a new instance. - /// - public static SearchParams FromUriParamList(IEnumerable> parameters) - { - var result = new SearchParams(); + foreach (var parameter in parameters) + result.Add(parameter.Item1, parameter.Item2); - foreach (var parameter in parameters) - result.Add(parameter.Item1, parameter.Item2); + return result; + } - return result; + /// + /// Convert this instance into a . + /// + public UriParamList ToUriParamList() + { + var result = new UriParamList(); + + if (!String.IsNullOrEmpty(Query)) result.Add(Tuple.Create(SEARCH_PARAM_QUERY, Query)); + if (!String.IsNullOrEmpty(Text)) result.Add(Tuple.Create(SEARCH_PARAM_TEXT, Text)); + if (!String.IsNullOrEmpty(Content)) result.Add(Tuple.Create(SEARCH_PARAM_CONTENT, Content)); + if (Count != null) result.Add(Tuple.Create(SEARCH_PARAM_COUNT, Count.Value.ToString())); + if (Include.Any()) result.AddRange(createIncludeParams(SEARCH_PARAM_INCLUDE, Include)); + if (RevInclude.Any()) result.AddRange(createIncludeParams(SEARCH_PARAM_REVINCLUDE, RevInclude)); + if (Sort.Any()) result.Add(createSortParam(Sort)); + if (Summary != null) result.Add(Tuple.Create(SEARCH_PARAM_SUMMARY, Summary.Value.ToString().ToLower())); + if (!String.IsNullOrEmpty(Filter)) result.Add(Tuple.Create(SEARCH_PARAM_FILTER, Filter)); + if (Contained != null) result.Add(Tuple.Create(SEARCH_PARAM_CONTAINED, Contained.Value.ToString().ToLower())); + if (ContainedType != null) result.Add(Tuple.Create(SEARCH_PARAM_CONTAINEDTYPE, ContainedType.Value.ToString().ToLower())); + if (Elements.Any()) result.Add(Tuple.Create(SEARCH_PARAM_ELEMENTS, String.Join(",", Elements))); + + result.AddRange(Parameters); + return result; + + Tuple createSortParam(IList<(string, SortOrder)> sorts) + { + var values = + from s in sorts + let orderPrefix = s.Item2 == SortOrder.Descending ? "-" : "" + select orderPrefix + s.Item1; + return new Tuple(SEARCH_PARAM_SORT, String.Join(",", values)); } - /// - /// Convert this instance into a . - /// - public UriParamList ToUriParamList() + IEnumerable> createIncludeParams(string paramtype, IList<(string path, IncludeModifier modifier)> includes) { - var result = new UriParamList(); - - if (!String.IsNullOrEmpty(Query)) result.Add(Tuple.Create(SEARCH_PARAM_QUERY, Query)); - if (!String.IsNullOrEmpty(Text)) result.Add(Tuple.Create(SEARCH_PARAM_TEXT, Text)); - if (!String.IsNullOrEmpty(Content)) result.Add(Tuple.Create(SEARCH_PARAM_CONTENT, Content)); - if (Count != null) result.Add(Tuple.Create(SEARCH_PARAM_COUNT, Count.Value.ToString())); - if (Include.Any()) result.AddRange(createIncludeParams(SEARCH_PARAM_INCLUDE, Include)); - if (RevInclude.Any()) result.AddRange(createIncludeParams(SEARCH_PARAM_REVINCLUDE, RevInclude)); - if (Sort.Any()) result.Add(createSortParam(Sort)); - if (Summary != null) result.Add(Tuple.Create(SEARCH_PARAM_SUMMARY, Summary.Value.ToString().ToLower())); - if (!String.IsNullOrEmpty(Filter)) result.Add(Tuple.Create(SEARCH_PARAM_FILTER, Filter)); - if (Contained != null) result.Add(Tuple.Create(SEARCH_PARAM_CONTAINED, Contained.Value.ToString().ToLower())); - if (ContainedType != null) result.Add(Tuple.Create(SEARCH_PARAM_CONTAINEDTYPE, ContainedType.Value.ToString().ToLower())); - if (Elements.Any()) result.Add(Tuple.Create(SEARCH_PARAM_ELEMENTS, String.Join(",", Elements))); - - result.AddRange(Parameters); - return result; - - Tuple createSortParam(IList<(string, SortOrder)> sorts) - { - var values = - from s in sorts - let orderPrefix = s.Item2 == SortOrder.Descending ? "-" : "" - select orderPrefix + s.Item1; - return new Tuple(SEARCH_PARAM_SORT, String.Join(",", values)); - } - - IEnumerable> createIncludeParams(string paramtype, IList<(string path, IncludeModifier modifier)> includes) - { - return from i in includes - let modifier = (i.modifier != IncludeModifier.None) ? SEARCH_MODIFIERSEPARATOR + i.modifier.GetLiteral() : "" - select new Tuple(paramtype + modifier, i.path); - - } + return from i in includes + let modifier = (i.modifier != IncludeModifier.None) ? SEARCH_MODIFIERSEPARATOR + i.modifier.GetLiteral() : "" + select new Tuple(paramtype + modifier, i.path); + } } +} + +/// +/// Possible values for the "_sort" parameter +/// +public enum SortOrder +{ + Ascending, + Descending +} + +/// +/// Possible values for the "_contained" parameter +/// +public enum ContainedSearch +{ + True, + False, + Both +} +/// +/// Possible "_include" modifiers +/// +public enum IncludeModifier +{ /// - /// Possible values for the "_sort" parameter + /// No modifier on the include/revinclude parameter /// - public enum SortOrder - { - Ascending, - Descending - } + None, + /// + /// R4 only: Allows the include/revinclude process to iterate + /// + [EnumLiteral("iterate")] + Iterate, + /// + /// STU3 only: Allows the include/revinclude process to recurse + /// + [EnumLiteral("recurse")] + Recurse +} +/// +/// Possible "_summary" values +/// +public enum SummaryType +{ /// - /// Possible values for the "_contained" parameter + /// Return only those elements marked as "summary" in the base definition of the resource(s) /// - public enum ContainedSearch - { - True, - False, - Both - } + [EnumLiteral("true")] + True, /// - /// Possible "_include" modifiers + /// Return only the "text" element, and any mandatory elements /// - public enum IncludeModifier - { - /// - /// No modifier on the include/revinclude parameter - /// - None, - /// - /// R4 only: Allows the include/revinclude process to iterate - /// - [EnumLiteral("iterate")] - Iterate, - /// - /// STU3 only: Allows the include/revinclude process to recurse - /// - [EnumLiteral("recurse")] - Recurse - } + [EnumLiteral("text")] + Text, /// - /// Possible "_summary" values + /// Remove the text element /// - public enum SummaryType - { - /// - /// Return only those elements marked as "summary" in the base definition of the resource(s) - /// - [EnumLiteral("true")] - True, - - /// - /// Return only the "text" element, and any mandatory elements - /// - [EnumLiteral("text")] - Text, - - /// - /// Remove the text element - /// - [EnumLiteral("data")] - Data, - - /// - /// Search only: just return a count of the matching resources, without returning the actual matches - /// - [EnumLiteral("count")] - Count, - - /// - /// Return all parts of the resource(s) - /// - [EnumLiteral("false")] - False - } + [EnumLiteral("data")] + Data, + /// + /// Search only: just return a count of the matching resources, without returning the actual matches + /// + [EnumLiteral("count")] + Count, /// - /// Values for the "_containedType" parameter. + /// Return all parts of the resource(s) /// - public enum ContainedResult - { - Container, - Contained - } + [EnumLiteral("false")] + False } -#nullable restore +/// +/// Values for the "_containedType" parameter. +/// +public enum ContainedResult +{ + Container, + Contained +} + +public static class SummaryTypeExtensions +{ + public static SerializationFilter? GetSerializationFilter(this SummaryType summary, string[]? elements = null, bool includeMandatoryInElementsSummary = false) + { + return summary switch + { + SummaryType.True => SerializationFilter.ForSummary(), + SummaryType.Text => SerializationFilter.ForText(), + SummaryType.Data => SerializationFilter.ForData(), + SummaryType.Count => SerializationFilter.ForCount(), + SummaryType.False when elements is not null => SerializationFilter.ForElements(elements, includeMandatoryInElementsSummary), + _ => null, + }; + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs index b3fd37c6a2..ecdcb5ee32 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs @@ -52,7 +52,7 @@ public class BaseFhirJsonPocoDeserializer /// /// Assembly containing the POCO classes to be used for deserialization. /// A settings object to be used by this instance. - public BaseFhirJsonPocoDeserializer(Assembly assembly, FhirJsonPocoDeserializerSettings settings) + public BaseFhirJsonPocoDeserializer(Assembly assembly, FhirJsonConverterOptions settings) { Settings = settings; _inspector = ModelInspector.ForAssembly(assembly ?? throw new ArgumentNullException(nameof(assembly))); @@ -63,7 +63,7 @@ public BaseFhirJsonPocoDeserializer(Assembly assembly, FhirJsonPocoDeserializerS /// /// The containing the POCO classes to be used for deserialization. /// A settings object to be used by this instance. - public BaseFhirJsonPocoDeserializer(ModelInspector inspector, FhirJsonPocoDeserializerSettings settings) + public BaseFhirJsonPocoDeserializer(ModelInspector inspector, FhirJsonConverterOptions settings) { Settings = settings; _inspector = inspector; @@ -72,7 +72,7 @@ public BaseFhirJsonPocoDeserializer(ModelInspector inspector, FhirJsonPocoDeseri /// /// The settings that were passed to the constructor. /// - public FhirJsonPocoDeserializerSettings Settings { get; } + public FhirJsonConverterOptions Settings { get; } private const string INSTANCE_VALIDATION_KEY_SUFFIX = ":instance"; private const string PROPERTY_VALIDATION_KEY_SUFFIX = ":property"; @@ -945,6 +945,4 @@ internal class FhirJsonPocoDeserializerState public readonly ExceptionAggregator Errors = new(); public readonly PathStack Path = new(); } -} - -#nullable restore \ No newline at end of file +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoSerializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonSerializer.cs similarity index 84% rename from src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoSerializer.cs rename to src/Hl7.Fhir.Base/Serialization/BaseFhirJsonSerializer.cs index c3beeab1ed..72067bf6be 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoSerializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonSerializer.cs @@ -10,13 +10,10 @@ using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; -using Hl7.Fhir.Specification; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Text.Json; namespace Hl7.Fhir.Serialization; @@ -27,51 +24,26 @@ namespace Hl7.Fhir.Serialization; /// The serializer uses the format documented in https://www.hl7.org/fhir/json.html. Since all POCOs included /// in the SDK implement IReadOnlyDictionary, these methods can be used to serialize POCOs to Json. /// -public class BaseFhirJsonPocoSerializer +public class BaseFhirJsonSerializer(ModelInspector inspector) { /// - /// Construct a new serializer for a specific release of FHIR. + /// The to be used for serialization metadata. /// - public BaseFhirJsonPocoSerializer(FhirRelease release) : this(release, new()) - { - // nothing - } + public ModelInspector Inspector => inspector; /// - /// Construct a new serializer for a specific release of FHIR. + /// Serializes the given POCO with FHIR data into Json. /// - public BaseFhirJsonPocoSerializer(FhirRelease release, FhirJsonPocoSerializerSettings settings) + /// The instance to serialize. + /// The to write the serialized data to. + /// An optional to use to serialize summaries. + public void Serialize(Base instance, Utf8JsonWriter writer, SerializationFilter? filter = null) { - Release = release; - Settings = settings; - } - - /// - /// The release of FHIR for which this serializer is configured. - /// - public FhirRelease Release { get; } - - /// - /// The settings that were passed to the constructor. - /// - public FhirJsonPocoSerializerSettings Settings { get; } + // If the element is summarized, add the subsetted tags. + if (filter is not null) + instance = SerializationUtil.MakeSubsettedClone(instance); - /// - /// Serializes the given dictionary with FHIR data into Json. - /// - public void Serialize(Base element, Utf8JsonWriter writer) => - serializeInternal(element, writer); - - /// - /// Serializes the given dictionary with FHIR data into a Json string. - /// - public string SerializeToString(Base element) - { - var stream = new MemoryStream(); - var writer = new Utf8JsonWriter(stream); - serializeInternal(element, writer); - writer.Flush(); - return Encoding.UTF8.GetString(stream.ToArray()); + serializeInternal(instance, writer, filter); } /// @@ -81,8 +53,8 @@ public string SerializeToString(Base element) /// with just the value, and one with the id/extensions. private void serializeInternal( Base? element, - Utf8JsonWriter writer - ) + Utf8JsonWriter writer, + SerializationFilter? filter) { if (element is null) { @@ -92,13 +64,12 @@ Utf8JsonWriter writer } writer.WriteStartObject(); - var filter = Settings.SummaryFilter; if (element is Resource r) writer.WriteString("resourceType", r.TypeName); // Only throw if we don't have a mapping where we are expected to: when this is a subclass of Base. - if (!ClassMapping.TryGetMappingForType(element.GetType(), Release, out var mapping)) + if (Inspector.FindOrImportClassMapping(element.GetType()) is not {} mapping) throw new InvalidOperationException($"Encountered type {element.GetType()}, which is a support POCO for FHIR, but does not " + $"have sufficient metadata to be used by the serializer."); @@ -123,10 +94,10 @@ Utf8JsonWriter writer switch (member.Value) { case PrimitiveType pt: - serializeFhirPrimitive(propertyName, pt, writer, requiredType); + serializeFhirPrimitive(propertyName, pt, writer, filter, requiredType); break; case IReadOnlyList pts: - serializeFhirPrimitiveList(propertyName, pts, writer, requiredType); + serializeFhirPrimitiveList(propertyName, pts, writer, filter, requiredType); break; case IReadOnlyList children: // Not List, since that is an invariant type. { @@ -134,7 +105,7 @@ Utf8JsonWriter writer writer.WriteStartArray(); foreach (var child in children) - serializeInternal(child, writer); + serializeInternal(child, writer, filter); writer.WriteEndArray(); break; @@ -142,11 +113,11 @@ Utf8JsonWriter writer case Base b: { writer.WritePropertyName(propertyName); - serializeInternal(b, writer); + serializeInternal(b, writer, filter); break; } default: - throw new InvalidOperationException($"GetElementPairs() returned a non-Base element of type {member.Value.GetType()}."); + throw new InvalidOperationException($"{nameof(element.EnumerateElements)} returned a non-Base element of type {member.Value.GetType()}."); } filter?.LeaveMember(member.Key, member.Value, propertyMapping); @@ -178,6 +149,7 @@ private void serializeFhirPrimitiveList( string elementName, IReadOnlyList values, Utf8JsonWriter writer, + SerializationFilter? filter, Type? requiredType = null) { if(values is null) throw new ArgumentNullException(nameof(values)); @@ -232,7 +204,7 @@ private void serializeFhirPrimitiveList( writeStartArray("_" + elementName, numNullsMissed, writer); } - serializeInternal(value, writer); + serializeInternal(value, writer, filter); } else { @@ -260,7 +232,7 @@ private static void writeStartArray(string propName, int numNulls, Utf8JsonWrite /// /// FHIR primitives are handled separately here since they may require /// serialization into two Json properties called "elementName" and "_elementName". - private void serializeFhirPrimitive(string elementName, PrimitiveType value, Utf8JsonWriter writer, Type? requiredType = null) + private void serializeFhirPrimitive(string elementName, PrimitiveType value, Utf8JsonWriter writer, SerializationFilter? filter, Type? requiredType = null) { if (value is null) throw new ArgumentNullException(nameof(value)); @@ -275,7 +247,7 @@ private void serializeFhirPrimitive(string elementName, PrimitiveType value, Utf // Write a property with '_elementName' writer.WritePropertyName("_" + elementName); - serializeInternal(value, writer); + serializeInternal(value, writer, filter); } /// @@ -329,4 +301,7 @@ protected virtual void SerializePrimitiveValue(object value, Utf8JsonWriter writ throw new FormatException($"There is no known serialization for type {value.GetType()} into a Json primitive property value."); } } -} \ No newline at end of file +} + +[Obsolete("This class has been replaced by the equivalent BaseFhirJsonSerializer class.")] +public class BaseFhirJsonPocoSerializer(ModelInspector inspector) : BaseFhirJsonSerializer(inspector); \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirSerializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirSerializer.cs deleted file mode 100644 index a63af6670c..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirSerializer.cs +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2018, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -#nullable enable - -using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Introspection; -using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; -using Hl7.Fhir.Utility; -using System.Linq; -#pragma warning disable CS0618 // Type or member is obsolete -#pragma warning disable SDK0001 - -namespace Hl7.Fhir.Serialization -{ - public class BaseFhirSerializer - { - public readonly SerializerSettings Settings; - private readonly ModelInspector _modelInspector; - private readonly Coding[] _subsettedTags = - new[] - { - new Coding("http://hl7.org/fhir/v3/ObservationValue", "SUBSETTED"), // STU3 Tag - new Coding("http://terminology.hl7.org/CodeSystem/v3-ObservationValue", "SUBSETTED"), // Tag from R4 and higher - }; - - public BaseFhirSerializer(ModelInspector modelInspector, SerializerSettings? settings = null) - { - Settings = settings?.Clone() ?? new SerializerSettings(); - _modelInspector = modelInspector; - } - - protected ITypedElement MakeElementStack(Base instance, SummaryType summary, string[]? elements) - => MakeElementStack(instance, summary, elements, false); - - protected ITypedElement MakeElementStack(Base instance, SummaryType summary, string[]? elements, bool includeMandatoryInElementsSummary) - { - if (summary == SummaryType.False && elements == null) return instance.ToTypedElementLegacy(_modelInspector); - - if (elements is not null && summary != SummaryType.False) - throw Error.Argument("elements", "Elements parameter is supported only when summary is SummaryType.False or summary is not specified at all."); - - var patchedInstance = (Base)instance.DeepCopy(); - - addSubsetted(patchedInstance, atRoot: true); - - var baseNav = new ScopedNode(patchedInstance.ToTypedElementLegacy(_modelInspector)); - - return summary switch - { - SummaryType.True => MaskingNode.ForSummary(baseNav), - SummaryType.Text => MaskingNode.ForText(baseNav), - SummaryType.Data => MaskingNode.ForData(baseNav), - SummaryType.Count => MaskingNode.ForCount(baseNav), - SummaryType.False => MaskingNode.ForElements(baseNav, elements, includeMandatoryInElementsSummary), - _ => baseNav, - }; - } - - // This is a hack to retain the capability to automatically add a SUBSETTED metatag to an - // instance, even if the current ITypedElement based serializer won't let you have that. - // I am not convinced it's the responsibility of the serializer (it's an outside policy), so - // it's just here to not break existing logic of the POCO serializers. - private void addSubsetted(Base instance, bool atRoot) - { - var isBundleAtRoot = instance is Bundle && atRoot; - - if (instance is Resource resource && !isBundleAtRoot) - { - resource.Meta ??= new Meta(); - - foreach (var item in _subsettedTags) - { - if (!resource.Meta.Tag.Any(t => t.System == item.System && t.Code == item.Code)) - { - resource.Meta.Tag.Add((Coding)item.DeepCopy()); - } - } - } - -#pragma warning disable CS0618 // Type or member is obsolete - foreach (var child in instance.Children()) -#pragma warning restore CS0618 // Type or member is obsolete - addSubsetted(child, atRoot: false); - } - } -} -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoSerializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlSerializer.cs similarity index 75% rename from src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoSerializer.cs rename to src/Hl7.Fhir.Base/Serialization/BaseFhirXmlSerializer.cs index f17219810e..99ce18c8a3 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoSerializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlSerializer.cs @@ -8,7 +8,6 @@ #nullable enable -using Hl7.Fhir.ElementModel; using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; using Hl7.Fhir.Specification; @@ -20,64 +19,52 @@ namespace Hl7.Fhir.Serialization; + /// /// Serializes the contents of a POCO according to the rules of FHIR Xml serialization. /// /// The serializer uses the format documented in https://www.hl7.org/fhir/xml.html. /// -public class BaseFhirXmlPocoSerializer +public class BaseFhirXmlSerializer(ModelInspector inspector) { /// - /// The release of FHIR for which this serializer is configured. + /// The to be used for serialization metadata. /// - public FhirRelease Release { get; } + public ModelInspector Inspector => inspector; /// - /// Construct a new serializer for a specific release of FHIR. - /// - public BaseFhirXmlPocoSerializer(FhirRelease release) - { - Release = release; - } - - /// - /// Serializes the given dictionary with FHIR data into Json. + /// Serializes the given POCO with FHIR data into Xml. /// + /// The instance to serialize. + /// The to write the serialized data to. + /// An optional to use to serialize summaries. + /// When serializing subtrees, the root element is named after the type of the instance. + /// If necessary, use this parameter to override the name of the root element. public void Serialize( - Base element, + Base instance, XmlWriter writer, - SerializationFilter? summary = default) + SerializationFilter? filter = null, + string? rootName = null) { - writer.WriteStartDocument(); + // If the element is summarized, add the subsetted tags. + if (filter is not null) + instance = SerializationUtil.MakeSubsettedClone(instance); - // If we are serializing a non-resource, or we are serializing a nested resource, - // we need to pick a name for the root element. - var pickElementName = element is not Resource or IScopedNode { Parent: not null }; - if (pickElementName) - { - // If we are an element with a name, pick that, otherwise us the name of the type. - var nodeName = element is ITypedElement ite ? ite.Name : element.TypeName; + writer.WriteStartDocument(); - writer.WriteStartElement(nodeName, XmlNs.FHIR); - } + // Wrap the instance with a named element if either a root name is given, + // or we are serializing a datatype (=a subtree). + if (rootName is not null) + writer.WriteStartElement(rootName, XmlNs.FHIR); + else if(instance is not Resource) + writer.WriteStartElement(instance.TypeName, XmlNs.FHIR); - serializeInternal(element, writer, summary); + serializeInternal(instance, writer, filter); - if (pickElementName) writer.WriteEndElement(); + if (rootName is not null) writer.WriteEndElement(); writer.WriteEndDocument(); } - /// - /// Serializes the given dictionary with FHIR data into UTF8 encoded Json. - /// - public string SerializeToString( - Base element, - SerializationFilter? summary = default) => - SerializationUtil.WriteXmlToString(element, (o,w) => Serialize(o, w, summary)); - - /// - /// Serializes the given PCO with FHIR data into XML. - /// private void serializeInternal( Base element, XmlWriter writer, @@ -87,7 +74,7 @@ private void serializeInternal( writer.WriteStartElement(r.TypeName, XmlNs.FHIR); // Only throw if we don't have a mapping where we are expected to: when this is a subclass of Base. - if (!ClassMapping.TryGetMappingForType(element.GetType(), Release, out var mapping)) + if (Inspector.FindOrImportClassMapping(element.GetType()) is not {} mapping) throw new InvalidOperationException($"Encountered type {element.GetType()}, which is a support POCO for FHIR, but does not " + $"have sufficient metadata to be used by the serializer."); @@ -128,7 +115,7 @@ private void serializeElement(Base element, XmlWriter writer, SerializationFilte var elementName = propertyMapping?.Choice == ChoiceType.DatatypeChoice ? addSuffixToElementName(mKey, serializeValue) : mKey; - if (serializeValue is IReadOnlyList coll) + if (serializeValue is IReadOnlyList coll) { foreach (var value in coll) serializeMemberValue(elementName, value, writer, filter); @@ -153,10 +140,12 @@ private static string addSuffixToElementName(string elementName, object? element } - private void serializeMemberValue(string elementName, object value, XmlWriter writer, SerializationFilter? filter) + private void serializeMemberValue(string elementName, object? value, XmlWriter writer, SerializationFilter? filter) { switch (value) { + case null: + break; // In error situations there may be a null in a list, just don't serialize it. case XHtml xhtml: writer.WriteRaw(xhtml.Value); break; @@ -203,4 +192,7 @@ protected virtual void SerializePrimitiveValue(string elementName, object value, writer.WriteAttributeString(elementName, ns: null, value: literal); } -} \ No newline at end of file +} + +[Obsolete("This class has been replaced by the equivalent BaseFhirXmlSerializer class.")] +public class BaseFhirXmlPocoSerializer(ModelInspector inspector) : BaseFhirXmlSerializer(inspector); \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/CommonFhirJsonSerializer.cs b/src/Hl7.Fhir.Base/Serialization/CommonFhirJsonSerializer.cs deleted file mode 100644 index 6043056978..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/CommonFhirJsonSerializer.cs +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2018, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -#nullable enable - -using Hl7.Fhir.Introspection; -using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Tasks = System.Threading.Tasks; - -namespace Hl7.Fhir.Serialization -{ - public class CommonFhirJsonSerializer : BaseFhirSerializer - { - public CommonFhirJsonSerializer(ModelInspector modelInspector, SerializerSettings? settings = null) : base(modelInspector, settings) - { - } - - private FhirJsonSerializationSettings buildFhirJsonWriterSettings() => - new() { Pretty = Settings.Pretty, AppendNewLine = Settings.AppendNewLine }; - - /// - public string SerializeToString(Base instance, SummaryType summary = SummaryType.False, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .ToJson(buildFhirJsonWriterSettings()); - - public async Tasks.Task SerializeToStringAsync(Base instance, SummaryType summary = SummaryType.False, string[]? elements = null) => - await MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .ToJsonAsync(buildFhirJsonWriterSettings()) - .ConfigureAwait(false); - - /// - public byte[] SerializeToBytes(Base instance, SummaryType summary = SummaryType.False, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .ToJsonBytes(buildFhirJsonWriterSettings()); - - public async Tasks.Task SerializeToBytesAsync(Base instance, SummaryType summary = SummaryType.False, string[]? elements = null) => - await MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .ToJsonBytesAsync(buildFhirJsonWriterSettings()) - .ConfigureAwait(false); - - public JObject SerializeToDocument(Base instance, SummaryType summary = SummaryType.False, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .ToJObject(buildFhirJsonWriterSettings()); - - /// - public void Serialize(Base instance, JsonWriter writer, SummaryType summary = SummaryType.False, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .WriteTo(writer, buildFhirJsonWriterSettings()); - - public async Tasks.Task SerializeAsync(Base instance, JsonWriter writer, SummaryType summary = SummaryType.False, string[]? elements = null) => - await MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .WriteToAsync(writer, buildFhirJsonWriterSettings()) - .ConfigureAwait(false); - } -} -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/CommonFhirXmlSerializer.cs b/src/Hl7.Fhir.Base/Serialization/CommonFhirXmlSerializer.cs deleted file mode 100644 index f5a876fc76..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/CommonFhirXmlSerializer.cs +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2018, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -#nullable enable - -using Hl7.Fhir.Introspection; -using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; -using Hl7.Fhir.Utility; -using System.Xml; -using System.Xml.Linq; -using Tasks = System.Threading.Tasks; - -namespace Hl7.Fhir.Serialization -{ - public class CommonFhirXmlSerializer : BaseFhirSerializer - { - public CommonFhirXmlSerializer(ModelInspector modelInspector, SerializerSettings? settings = null) : base(modelInspector, settings) - { - } - - private FhirXmlSerializationSettings buildFhirXmlWriterSettings() => - new() { Pretty = Settings.Pretty, AppendNewLine = Settings.AppendNewLine, TrimWhitespaces = Settings.TrimWhiteSpacesInXml }; - - /// - public string SerializeToString(Base instance, SummaryType summary = SummaryType.False, string? root = null, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .Rename(root) - .ToXml(settings: buildFhirXmlWriterSettings()); - - public async Tasks.Task SerializeToStringAsync(Base instance, SummaryType summary = SummaryType.False, string? root = null, string[]? elements = null) => - await MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .Rename(root) - .ToXmlAsync(settings: buildFhirXmlWriterSettings()) - .ConfigureAwait(false); - - /// - public byte[] SerializeToBytes(Base instance, SummaryType summary = SummaryType.False, string? root = null, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .Rename(root) - .ToXmlBytes(settings: buildFhirXmlWriterSettings()); - - public async Tasks.Task SerializeToBytesAsync(Base instance, SummaryType summary = SummaryType.False, string? root = null, string[]? elements = null) => - await MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .Rename(root) - .ToXmlBytesAsync(settings: buildFhirXmlWriterSettings()) - .ConfigureAwait(false); - - public XDocument SerializeToDocument(Base instance, SummaryType summary = SummaryType.False, string? root = null, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .Rename(root) - .ToXDocument(buildFhirXmlWriterSettings()).Rename(root); - - /// - public void Serialize(Base instance, XmlWriter writer, SummaryType summary = SummaryType.False, string? root = null, string[]? elements = null) => - MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .Rename(root) - .WriteTo(writer, settings: buildFhirXmlWriterSettings()); - - public async Tasks.Task SerializeAsync(Base instance, XmlWriter writer, SummaryType summary = SummaryType.False, string? root = null, string[]? elements = null) => - await MakeElementStack(instance, summary, elements, Settings?.IncludeMandatoryInElementsSummary ?? false) - .Rename(root) - .WriteToAsync(writer, settings: buildFhirXmlWriterSettings()) - .ConfigureAwait(false); - } -} -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilder.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilder.cs index c5ac6003fc..7de495d7b9 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilder.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilder.cs @@ -19,12 +19,6 @@ namespace Hl7.Fhir.Serialization { internal class FhirJsonBuilder : IExceptionSource { - internal FhirJsonBuilder(FhirJsonSerializationSettings settings = null) - { - _settings = settings?.Clone() ?? new FhirJsonSerializationSettings(); - } - - private FhirJsonSerializationSettings _settings; private bool _roundtripMode = true; public ExceptionNotificationHandler ExceptionHandler { get; set; } @@ -137,16 +131,8 @@ internal bool MustSerializeMember(ITypedElement source, out IElementDefinitionSu { var message = $"Element '{source.Location}' is missing type information."; - if (_settings.IgnoreUnknownElements) - { - ExceptionHandler.NotifyOrThrow(source, ExceptionNotification.Warning( - new MissingTypeInformationException(message))); - } - else - { - ExceptionHandler.NotifyOrThrow(source, ExceptionNotification.Error( - new MissingTypeInformationException(message))); - } + ExceptionHandler.NotifyOrThrow(source, ExceptionNotification.Warning( + new MissingTypeInformationException(message))); return false; } @@ -208,4 +194,4 @@ private void addChildren(ITypedElement node, JObject parent) _ => new JValue(PrimitiveTypeConverter.ConvertTo(value)), }; } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilderExtensions.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilderExtensions.cs index cc12c2ccaa..c617cf0f8f 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilderExtensions.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilderExtensions.cs @@ -15,80 +15,120 @@ using Newtonsoft.Json.Linq; using System; using System.Runtime.CompilerServices; -using System.Text.Json; using System.Threading.Tasks; -using JsonSerializer = System.Text.Json.JsonSerializer; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +public static class FhirJsonBuilderExtensions { - public static class FhirJsonBuilderExtensions + /// + /// Serializes an instance to FHIR Json. + /// + /// The instance to serialize. + /// The to write the serialized data to. + public static void WriteTo(this ITypedElement source, JsonWriter writer) => + new FhirJsonBuilder().Build(source).writeTo(writer); + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task WriteToAsync(this ITypedElement source, JsonWriter destination) => + await new FhirJsonBuilder().Build(source).writeToAsync(destination).ConfigureAwait(false); + + /// + /// Serializes an instance into FHIR Json. + /// + /// The instance to serialize. + /// The to write the serialized data to. + /// Since has no type information, this function will throw unless + /// the originated from parsing using . + public static void WriteTo(this ISourceNode source, JsonWriter writer) => + new FhirJsonBuilder().Build(source).writeTo(writer); + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task WriteToAsync(this ISourceNode source, JsonWriter destination) => + await new FhirJsonBuilder().Build(source).writeToAsync(destination).ConfigureAwait(false); + + /// + /// Serializes an instance into a . + /// + /// The instance to serialize. + /// Since has no type information, this function will throw unless + /// the originated from parsing using . + public static JObject ToJObject(this ISourceNode source) => new FhirJsonBuilder().Build(source); + + /// + /// Serializes an instance into a + /// + /// The instance to serialize. + public static JObject ToJObject(this ITypedElement source) => new FhirJsonBuilder().Build(source); + + /// + /// Serializes an instance into a FHIR Json string. + /// + /// The instance to serialize. + /// Formats and indents the serialized Json. + public static string ToJson(this ITypedElement source, bool pretty = false) + { + if (source is not PocoNode { Poco: Resource resource } node) + return SerializationUtil.WriteJsonToString(source.WriteTo, pretty); + + var inspector = node.FindInspector() ?? ModelInspector.ForType(resource.GetType()); + var ser = new BaseFhirJsonSerializer(inspector); + return ser.SerializeToString(resource, pretty); + } + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task ToJsonAsync(this ITypedElement source, bool pretty = false) + { + if (source is not PocoNode { Poco: Resource resource } node) + return await SerializationUtil + .WriteJsonToStringAsync(async writer => await source.WriteToAsync(writer).ConfigureAwait(false), + pretty).ConfigureAwait(false); + + var inspector = node.FindInspector() ?? ModelInspector.ForType(resource.GetType()); + var ser = new BaseFhirJsonSerializer(inspector); + return ser.SerializeToString(resource, pretty); + } + + /// + /// Serializes an instance into a FHIR Json string. + /// + /// The instance to serialize. + /// Formats and indents the serialized Json. + /// Since has no type information, this function will throw unless + /// the originated from parsing using . + public static string ToJson(this ISourceNode source, bool pretty = false) + => SerializationUtil.WriteJsonToString(source.WriteTo, pretty); + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task ToJsonAsync(this ISourceNode source, bool pretty = false) + => await SerializationUtil.WriteJsonToStringAsync(source.WriteToAsync, pretty).ConfigureAwait(false); + + /// + /// Serializes an instance into a FHIR Json byte array. + /// + /// The instance to serialize. + /// Formats and indents the serialized Json. + public static byte[] ToJsonBytes(this ITypedElement source, bool pretty = false) + => SerializationUtil.WriteJsonToBytes(source.WriteTo, pretty); + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task ToJsonBytesAsync(this ITypedElement source, bool pretty = false) + => await SerializationUtil.WriteJsonToBytesAsync(source.WriteToAsync, pretty).ConfigureAwait(false); + + private static void writeTo(this JObject root, JsonWriter destination) + { + root.WriteTo(destination); + destination.Flush(); + } + + private static async Task writeToAsync(this JObject root, JsonWriter destination) { - /// - private static void writeTo(this JObject root, JsonWriter destination, string rootName = null) - { - root.WriteTo(destination); - destination.Flush(); - } - - private static async Task writeToAsync(this JObject root, JsonWriter destination, string rootName = null) - { - await root.WriteToAsync(destination).ConfigureAwait(false); - await destination.FlushAsync().ConfigureAwait(false); - } - - /// - public static void WriteTo(this ITypedElement source, JsonWriter destination, FhirJsonSerializationSettings settings = null) => - new FhirJsonBuilder(settings).Build(source).writeTo(destination); - - public static async Task WriteToAsync(this ITypedElement source, JsonWriter destination, FhirJsonSerializationSettings settings = null) => - await new FhirJsonBuilder(settings).Build(source).writeToAsync(destination).ConfigureAwait(false); - - /// - public static void WriteTo(this ISourceNode source, JsonWriter destination, FhirJsonSerializationSettings settings = null) => - new FhirJsonBuilder(settings).Build(source).writeTo(destination); - - public static async Task WriteToAsync(this ISourceNode source, JsonWriter destination, FhirJsonSerializationSettings settings = null) => - await new FhirJsonBuilder(settings).Build(source).writeToAsync(destination).ConfigureAwait(false); - - public static JObject ToJObject(this ISourceNode source, FhirJsonSerializationSettings settings = null) => - new FhirJsonBuilder(settings).Build(source); - - public static JObject ToJObject(this ITypedElement source, FhirJsonSerializationSettings settings = null) => - new FhirJsonBuilder(settings).Build(source); - - /// - [TemporarilyChanged] // This works. Remove this attribute after writing new extensions on the pocos - public static string ToJson(this ITypedElement source, FhirJsonSerializationSettings settings = null) - { - if (source is not PocoNode {Poco: Resource resource} node) - return SerializationUtil.WriteJsonToString(writer => source.WriteTo(writer, settings), settings?.Pretty ?? false, settings?.AppendNewLine ?? false); - - var options = new JsonSerializerOptions{ WriteIndented = settings?.Pretty ?? false }.ForFhir(node.FindInspector() ?? ModelInspector.ForType(resource.GetType())); - return JsonSerializer.Serialize(resource, options) + (settings?.AppendNewLine == true ? "\n" : ""); - } - - [TemporarilyChanged] // This works. Remove this attribute after writing new extensions on the pocos - public static async Task ToJsonAsync(this ITypedElement source, FhirJsonSerializationSettings settings = null) - { - if (source is not PocoNode {Poco: Resource resource} node) - return await SerializationUtil.WriteJsonToStringAsync(async writer => await source.WriteToAsync(writer, settings).ConfigureAwait(false), settings?.Pretty ?? false, settings?.AppendNewLine ?? false).ConfigureAwait(false); - - var options = new JsonSerializerOptions{ WriteIndented = settings?.Pretty ?? false }.ForFhir(node.FindInspector() ?? ModelInspector.ForType(resource.GetType())); - return JsonSerializer.Serialize(resource, options) + (settings?.AppendNewLine == true ? "\n" : ""); - } - - /// - public static string ToJson(this ISourceNode source, FhirJsonSerializationSettings settings = null) - => SerializationUtil.WriteJsonToString(writer => source.WriteTo(writer, settings), settings?.Pretty ?? false, settings?.AppendNewLine ?? false); - - public static async Task ToJsonAsync(this ISourceNode source, FhirJsonSerializationSettings settings = null) - => await SerializationUtil.WriteJsonToStringAsync(async writer => await source.WriteToAsync(writer, settings).ConfigureAwait(false), settings?.Pretty ?? false, settings?.AppendNewLine ?? false).ConfigureAwait(false); - - /// - public static byte[] ToJsonBytes(this ITypedElement source, FhirJsonSerializationSettings settings = null) - => SerializationUtil.WriteJsonToBytes(writer => source.WriteTo(writer, settings)); - - public static async Task ToJsonBytesAsync(this ITypedElement source, FhirJsonSerializationSettings settings = null) - => await SerializationUtil.WriteJsonToBytesAsync(async writer => await source.WriteToAsync(writer, settings).ConfigureAwait(false)).ConfigureAwait(false); + await root.WriteToAsync(destination).ConfigureAwait(false); + await destination.FlushAsync().ConfigureAwait(false); } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverter.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverter.cs index c8b4f2541a..612fedfcd2 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverter.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverter.cs @@ -10,109 +10,99 @@ using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; -using Hl7.Fhir.Utility; using System; using System.Collections.Generic; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +/// +/// A converter factory to construct FhirJsonConverters for subclasses of . +/// +public class FhirJsonConverterFactory(ModelInspector inspector, FhirJsonConverterOptions converterOptions) : JsonConverterFactory { - /// - /// A converter factory to construct FhirJsonConverters for subclasses of . - /// - public class FhirJsonConverterFactory(ModelInspector inspector, FhirJsonPocoSerializerSettings serializerSettings, FhirJsonPocoDeserializerSettings deserializerSettings) : JsonConverterFactory + internal PocoSerializationEngine? Engine { get; private set; } + + private PocoSerializationEngine createDefaultEngine() { - internal PocoSerializationEngine? Engine { get; set; } + return (PocoSerializationEngine)FhirSerializationEngineFactory.Strict(inspector, converterOptions); + } - private PocoSerializationEngine createDefaultEngine() - { - return (PocoSerializationEngine)FhirSerializationEngineFactory.Strict(inspector, serializerSettings, deserializerSettings); - } + public FhirJsonConverterFactory( + Assembly assembly, FhirJsonConverterOptions converterOptions) : this(ModelInspector.ForAssembly(assembly), converterOptions) + { + // Nothing + } - public FhirJsonConverterFactory( - Assembly assembly, FhirJsonPocoSerializerSettings serializerSettings, FhirJsonPocoDeserializerSettings deserializerSettings) : this(ModelInspector.ForAssembly(assembly), serializerSettings, deserializerSettings) - { - // Nothing - } + internal void SetEnforcedErrors(IEnumerable toEnforce) + { + Engine ??= createDefaultEngine(); + Engine.IgnoreFilter = Engine.IgnoreFilter.And(toEnforce.ToPredicate().Negate()); + } - internal void SetEnforcedErrors(IEnumerable toEnforce) - { - Engine ??= createDefaultEngine(); - Engine.IgnoreFilter = Engine.IgnoreFilter.And(toEnforce.ToPredicate().Negate()); - } + internal void SetIgnoredErrors(IEnumerable toIgnore) + { + Engine ??= createDefaultEngine(); + Engine.IgnoreFilter = Engine.IgnoreFilter.Or(toIgnore.ToPredicate()); + } - internal void SetIgnoredErrors(IEnumerable toIgnore) + internal void SetMode(DeserializerModes mode) + { + Engine = mode switch { - Engine ??= createDefaultEngine(); - Engine.IgnoreFilter = Engine.IgnoreFilter.Or(toIgnore.ToPredicate()); - } + DeserializerModes.Recoverable => (PocoSerializationEngine)FhirSerializationEngineFactory.Recoverable(inspector, converterOptions), + DeserializerModes.BackwardsCompatible => (PocoSerializationEngine)FhirSerializationEngineFactory.BackwardsCompatible(inspector, converterOptions), + DeserializerModes.Ostrich => (PocoSerializationEngine)FhirSerializationEngineFactory.Ostrich(inspector, converterOptions), + _ => createDefaultEngine() + }; + } - internal void SetMode(DeserializerModes mode) - { - Engine = mode switch - { - DeserializerModes.Recoverable => (PocoSerializationEngine)FhirSerializationEngineFactory.Recoverable(inspector, serializerSettings, deserializerSettings), - DeserializerModes.BackwardsCompatible => (PocoSerializationEngine)FhirSerializationEngineFactory.BackwardsCompatible(inspector, serializerSettings, deserializerSettings), - DeserializerModes.Ostrich => (PocoSerializationEngine)FhirSerializationEngineFactory.Ostrich(inspector, serializerSettings, deserializerSettings), - _ => createDefaultEngine() - }; - } - - public override bool CanConvert(Type typeToConvert) => typeof(Base).IsAssignableFrom(typeToConvert); - - public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - Engine ??= createDefaultEngine(); - return (JsonConverter?)Activator.CreateInstance( - typeof(FhirJsonConverter<>).MakeGenericType(typeToConvert), BindingFlags.NonPublic | BindingFlags.Instance, null, - [Engine], null, null); - } + public override bool CanConvert(Type typeToConvert) => typeof(Base).IsAssignableFrom(typeToConvert); + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + Engine ??= createDefaultEngine(); + return (JsonConverter?)Activator.CreateInstance( + typeof(FhirJsonConverter<>).MakeGenericType(typeToConvert), BindingFlags.NonPublic | BindingFlags.Instance, null, + [Engine], null, null); } +} +/// +/// FHIR Resource and datatype converter for FHIR deserialization. +/// +public class FhirJsonConverter : JsonConverter + where TF : Base +{ + private readonly PocoSerializationEngine _engine; - /// - /// FHIR Resource and datatype converter for FHIR deserialization. - /// - public class FhirJsonConverter : JsonConverter - where F : Base + private FhirJsonConverter(IFhirSerializationEngine engine) { - private readonly PocoSerializationEngine _engine; + this._engine = (PocoSerializationEngine)engine; + } - private FhirJsonConverter(IFhirSerializationEngine engine) - { - this._engine = (PocoSerializationEngine)engine; - } - - /// - /// Determines whether the specified type can be converted. - /// - public override bool CanConvert(Type objectType) => typeof(F) == objectType; - - /// - /// The filter used to serialize a summary of the resource. - /// - public SerializationFilter? SerializationFilter { get; } - - /// - /// Writes a specified value as JSON. - /// - public override void Write(Utf8JsonWriter writer, F poco, JsonSerializerOptions options) - { - _engine.SerializeToJsonWriter(poco, writer); - } + /// + /// Determines whether the specified type can be converted. + /// + public override bool CanConvert(Type objectType) => typeof(TF) == objectType; - /// - /// Reads and converts the JSON to a typed object. - /// - public override F Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return typeof(Resource).IsAssignableFrom(typeToConvert) - ? (F)(Base)_engine.DeserializeFromJson(ref reader) - : (F)_engine.DeserializeObjectFromJson(typeToConvert, ref reader); - } + /// + /// Writes a specified value as JSON. + /// + public override void Write(Utf8JsonWriter writer, TF poco, JsonSerializerOptions options) + { + _engine.SerializeToJsonWriter(poco, writer); } -} -#nullable restore \ No newline at end of file + /// + /// Reads and converts the JSON to a typed object. + /// + public override TF Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return typeof(Resource).IsAssignableFrom(typeToConvert) + ? (TF)(Base)_engine.DeserializeFromJson(ref reader) + : (TF)_engine.DeserializeObjectFromJson(typeToConvert, ref reader); + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptions.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptions.cs new file mode 100644 index 0000000000..7aa407a15c --- /dev/null +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptions.cs @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + + +#nullable enable + +using Hl7.Fhir.Model; +using System; +using System.Text.Json; + +namespace Hl7.Fhir.Serialization; + +/// +/// Specify the optional features for Json deserialization. +/// +public record FhirJsonConverterOptions +{ + /// + /// Specifies the filter to use for summary serialization. + /// + public SerializationFilter? SummaryFilter { get; set; } = default; + + /// + /// If the caller will not access base64 data in the deserialized resources, base64 decoding + /// of values can be turned off to increase performance. + /// + /// The element's will + /// still contain the unparsed base64 data and will therefore be retained and round-tripped. + public bool DisableBase64Decoding { get; init; } = false; + + /// + /// If set, this delegate is called when the deserializer fails to parse a primitive json value. + /// + public PrimitiveParseHandler? OnPrimitiveParseFailed { get; init; } = null; + + /// + /// If set, this validator is invoked before the value is set in the object under construction to validate + /// and possibly alter the value. Setting this property to null will disable validation completely. + /// + public IDeserializationValidator? Validator { get; init; } = DataAnnotationDeserialzationValidator.Default; + + /// + /// Perform the parse time validation on the deserialized object even if parsing issues occurred. + /// + /// + /// This is useful for "strict mode" once pass validators and may result in spurious error messages + /// from validating incomplete content. + /// + public bool ValidateOnFailedParse { get; init; } = false; + + /// + /// During parsing any contained resources (such as those in a bundle) that encounter some form of parse/validation exception + /// will have a List<CodedException> of these exceptions added as an annotation to the child resource. + /// + /// + /// This is primarily added to ease the processing of bundles during a batch submission. + /// (without requiring processing fhirpath expressions in the issues in the parsing operation outcome to determine if a + /// resource was clean and possibly ok to process). + /// + public bool AnnotateResourceParseExceptions { get; init; } = false; +} + +/// +/// A callback that can handle parsing failures for primitive types. +/// +/// A json reader positioned on the primitive value that failed to parse. +/// The .NET type the deserializer needs this handler to return to be able to update the POCO under construction. +/// The value the deserializer would have returned if this handler was not installed. +/// The exception the deserializer would have raised if this handler was not installed. +/// Returns an object, an error or both depending on whether the handler succeeded in parsing. The returned exception +/// may be the originalException and the returned value the originalValue. +/// If both are returned, the parsing is considered to have failed, with the returned object as the partially parsed result. +public delegate (object?, FhirJsonException?) PrimitiveParseHandler( + ref Utf8JsonReader reader, + Type targetType, + object? originalValue, + FhirJsonException originalException); \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptionsExtensions.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptionsExtensions.cs new file mode 100644 index 0000000000..26be1142c1 --- /dev/null +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptionsExtensions.cs @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2021, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +using Hl7.Fhir.Introspection; +using Hl7.Fhir.Model; +using Hl7.Fhir.Model.CdsHooks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; + +namespace Hl7.Fhir.Serialization; + +/// +/// Utility extension method to initialize the to use the System.Text.Json +/// based (de)serializers. +/// +public static class FhirJsonConverterOptionsExtensions +{ + /// + /// Initialize the options to serialize using the CDS Hooks specific (de)serializers. Note that this also adds the FHIR specific converters, since FHIR is expected in the CDS Hooks messages. + /// +#if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.Experimental(diagnosticId: "ExperimentalApi")] +#else + [System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] +#endif + public static JsonSerializerOptions ForCdsHooks(this JsonSerializerOptions options, ModelInspector inspector, + FhirJsonConverterOptions? deserializerSettings = null) => + options.ForFhir(inspector, deserializerSettings ?? new FhirJsonConverterOptions()).addCdsHooks(); + + +#if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.Experimental(diagnosticId: "ExperimentalApi")] +#else + [System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] +#endif + public static JsonSerializerOptions ForCdsHooks(this JsonSerializerOptions options, Assembly modelAssembly, + FhirJsonConverterOptions? deserializerSettings = null) => + options.ForFhir(modelAssembly, deserializerSettings ?? new FhirJsonConverterOptions()) + .addCdsHooks(); + + private static JsonSerializerOptions addCdsHooks(this JsonSerializerOptions options) + { + options.TypeInfoResolver = (options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver()).WithAddedModifier(changeCdsHookPropertyNames); + + return options; + + static void changeCdsHookPropertyNames(JsonTypeInfo ti) + { + if (ti.Type.GetCustomAttribute() is null) return; + + foreach (var p in ti.Properties) + p.Name = char.ToLower(p.Name.First()) + p.Name.Substring(1); + } + } + + /// + /// Initialize the options to serialize using the JsonFhirConverter, producing compact output without whitespace. + /// + public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, Assembly modelAssembly) => + options.ForFhir(modelAssembly, new FhirJsonConverterOptions()); + + public static JsonSerializerOptions ForFhir( + this JsonSerializerOptions options, + Assembly modelAssembly, + FhirJsonConverterOptions converterOptions + ) + { + var converter = new FhirJsonConverterFactory(modelAssembly, converterOptions); + return options.ForFhir(converter); + } + + /// + public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, ModelInspector inspector) => + options.ForFhir(inspector, new FhirJsonConverterOptions()); + + /// + public static JsonSerializerOptions ForFhir( + this JsonSerializerOptions options, + ModelInspector inspector, + FhirJsonConverterOptions converterOptions + ) + { + var converter = new FhirJsonConverterFactory(inspector, converterOptions); + return options.ForFhir(converter); + } + + /// + /// Initialize the options to serialize using the JsonFhirConverterFactory, producing compact output without whitespace. + /// + public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, FhirJsonConverterFactory converter) + { + options.Converters.Add(converter); + options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + + return options; + } + + /// + /// Initialize the options to serialize using the JsonFhirConverter, producing compact output without whitespace. + /// + public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, FhirJsonConverter converter) where F : Base + { + options.Converters.Add(converter); + options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + + return options; + } + + /// + /// Modify the options to use a preset list of errors to ignore by specifying a mode. This can be any member of + /// + /// + /// Modifying the options is always left-associative. This means that defining custom constraints should probably be done AFTER setting the mode. + /// + public static JsonSerializerOptions UsingMode(this JsonSerializerOptions options, DeserializerModes mode) + { + getCustomFactoryFromList(options.Converters).SetMode(mode); + return options; + } + + /// + /// Modify the options to use a custom list of errors to enforce. + /// + /// + /// - Modifying the options is always left-associative. This means that defining custom constraints should probably be done AFTER setting the mode. + /// - This also means that enforcing, then ignoring an error has the opposite behaviour as ignoring it, then enforcing it. + /// + public static JsonSerializerOptions Enforcing(this JsonSerializerOptions options, IEnumerable toEnforce) + { + getCustomFactoryFromList(options.Converters).SetEnforcedErrors(toEnforce); + return options; + } + + /// + /// Modify the options to use a custom list of errors to ignore. + /// + /// + /// - Modifying the options is always left-associative. This means that defining custom constraints should probably be done AFTER setting the mode. + /// - This also means that enforcing, then ignoring an error has the opposite behaviour as ignoring it, then enforcing it. + /// + public static JsonSerializerOptions Ignoring(this JsonSerializerOptions options, IEnumerable toIgnore) + { + getCustomFactoryFromList(options.Converters).SetIgnoredErrors(toIgnore); + return options; + } + + /// + /// Initialize the options to serialize using the JsonFhirConverter, producing compact output without whitespace. + /// + public static JsonSerializerOptions Compact(this JsonSerializerOptions options) + { + options.WriteIndented = false; + return options; + } + + /// + /// Initialize the options to serialize using the JsonFhirConverter, producing pretty output. + /// + public static JsonSerializerOptions Pretty(this JsonSerializerOptions options) + { + options.WriteIndented = true; + return options; + } + + // internal for testing purposes + internal static JsonConverter? FindCustomConverter(this IEnumerable converters) + { + return converters.FirstOrDefault(jsonConverter => jsonConverter.CanConvert(typeof(Resource))); + } + + private static FhirJsonConverterFactory getCustomFactoryFromList(IEnumerable converters) + { + return FindCustomConverter(converters) as FhirJsonConverterFactory ?? throw new NotSupportedException( + "Customizing a FHIR serializer can only be done after it was created. Try calling .ForFhir first"); + } +} + +/// +/// Enumerates the modes with which a deserializer can be configured +/// +public enum DeserializerModes +{ + /// + /// Do not ignore any errors (default behaviour for most implementations) + /// + Strict, + + /// + /// An issue is recoverable if all data present in the parsed data could be retrieved and + /// captured in the POCO model, even if the syntax or the data was not fully FHIR compliant. + /// + Recoverable, + + /// + /// An issue is allowable for backwards compatibility if it could be caused because an older parser encounters data coming from a newer + /// FHIR release. This means allowing unknown elements, attributes, codes and types in a choice element. Note that the POCO model cannot capture + /// these newer elements and data, so this means data loss may occur. + /// + BackwardsCompatible, + + /// + /// Ignore all errors. Useful for debugging and/or when you know the data to be parsed is a correct instance. + /// + Ostrich, +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoDeserializerSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoDeserializerSettings.cs deleted file mode 100644 index 84e65b7e45..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoDeserializerSettings.cs +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2021, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - - -#nullable enable - -using Hl7.Fhir.Model; -using System; -using System.Text.Json; - -namespace Hl7.Fhir.Serialization -{ - /// - /// Specify the optional features for Json deserialization. - /// - public record FhirJsonPocoDeserializerSettings - { - /// - /// If the caller will not access base64 data in the deserialized resources, base64 decoding - /// of values can be turned off to increase performance. - /// - /// The element's will - /// still contain the unparsed base64 data and will therefore be retained and round-tripped. - public bool DisableBase64Decoding { get; init; } = false; - - /// - /// If set, this delegate is called when the deserializer fails to parse a primitive json value. - /// - public PrimitiveParseHandler? OnPrimitiveParseFailed { get; init; } = null; - - /// - /// If set, this validator is invoked before the value is set in the object under construction to validate - /// and possibly alter the value. Setting this property to null will disable validation completely. - /// - public IDeserializationValidator? Validator { get; init; } = DataAnnotationDeserialzationValidator.Default; - - /// - /// Perform the parse time validation on the deserialized object even if parsing issues occurred. - /// - /// - /// This is useful for "strict mode" once pass validators and may result in spurious error messages - /// from validating incomplete content. - /// - public bool ValidateOnFailedParse { get; init; } = false; - - /// - /// During parsing any contained resources (such as those in a bundle) that encounter some form of parse/validation exception - /// will have a List<CodedException> of these exceptions added as an annotation to the child resource. - /// - /// - /// This is primarily added to ease the processing of bundles during a batch submission. - /// (without requiring processing fhirpath expressions in the issues in the parsing operation outcome to determine if a - /// resource was clean and possibly ok to process). - /// - public bool AnnotateResourceParseExceptions { get; init; } = false; - } - - /// - /// A callback that can handle parsing failures for primitive types. - /// - /// A json reader positioned on the primitive value that failed to parse. - /// The .NET type the deserializer needs this handler to return to be able to update the POCO under construction. - /// The value the deserializer would have returned if this handler was not installed. - /// The exception the deserializer would have raised if this handler was not installed. - /// Returns an object, an error or both depending on whether the handler succeeded in parsing. The returned exception - /// may be the originalException and the returned value the originalValue. - /// If both are returned, the parsing is considered to have failed, with the returned object as the partially parsed result. - public delegate (object?, FhirJsonException?) PrimitiveParseHandler( - ref Utf8JsonReader reader, - Type targetType, - object? originalValue, - FhirJsonException originalException); -} - -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoSerializerSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoSerializerSettings.cs deleted file mode 100644 index 8423e77da9..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoSerializerSettings.cs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2021, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - - - - -#nullable enable - -namespace Hl7.Fhir.Serialization -{ - /// - /// Specify the optional features for Json serialization. - /// - public record FhirJsonPocoSerializerSettings - { - /// - /// Specifies the filter to use for summary serialization. - /// - public SerializationFilter? SummaryFilter { get; set; } = default; - } -} - -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonSerializationSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonSerializationSettings.cs deleted file mode 100644 index 6d0f18467e..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonSerializationSettings.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2018, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE - */ - - - -using Hl7.Fhir.Utility; -using System; - -namespace Hl7.Fhir.Serialization -{ - /// Configuration settings for the class. - public class FhirJsonSerializationSettings - { - /// - /// When encountering a member without type information, just skip it instead of reporting an error. - /// - public bool IgnoreUnknownElements { get; set; } // = false; - - /// - /// Format the json output when converted to a string. - /// - public bool Pretty { get; set; } // = false; - - /// - /// Add new line at the end of the json output when converted to a string. - /// - public bool AppendNewLine { get; set; } // = false; - - /// Default constructor. Creates a new instance with default property values. - public FhirJsonSerializationSettings() { } - - /// Clone constructor. Generates a new instance initialized from the state of the specified instance. - /// The specified argument is null. - public FhirJsonSerializationSettings(FhirJsonSerializationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - other.CopyTo(this); - } - - /// Copy all configuration settings to another instance. - /// Another instance. - /// The specified argument is null. - public void CopyTo(FhirJsonSerializationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - - other.IgnoreUnknownElements = IgnoreUnknownElements; - other.Pretty = Pretty; - other.AppendNewLine = AppendNewLine; - } - - /// Creates a new object that is a copy of the current instance. - public FhirJsonSerializationSettings Clone() => new(this); - - /// Creates a new instance with default property values. - public static FhirJsonSerializationSettings CreateDefault() => new(); - } -} diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilder.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilder.cs index 114b689f42..a28916df69 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilder.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilder.cs @@ -15,207 +15,189 @@ using System.Xml; using System.Xml.Linq; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +internal class FhirXmlBuilder : IExceptionSource { - internal class FhirXmlBuilder : IExceptionSource - { - internal FhirXmlBuilder(FhirXmlSerializationSettings settings = null) - { - _settings = settings?.Clone() ?? new FhirXmlSerializationSettings(); - } + private bool _roundtripMode; - private FhirXmlSerializationSettings _settings; - private bool _roundtripMode; + public ExceptionNotificationHandler ExceptionHandler { get; set; } - public ExceptionNotificationHandler ExceptionHandler { get; set; } + public XDocument Build(ITypedElement source) => + buildInternal(source); - public XDocument Build(ITypedElement source) => - buildInternal(source); + public XDocument Build(ISourceNode source) + { + bool hasXmlSource = source.Annotation() != null; - public XDocument Build(ISourceNode source) + // We can only work with an untyped source if we're doing a roundtrip, + // so we have all serialization details available. + if (!hasXmlSource) { - bool hasXmlSource = source.Annotation() != null; + throw Error.NotSupported($"The {nameof(FhirXmlBuilder)} will only work correctly on an untyped " + + $"source if the source is a {nameof(FhirXmlNode)}."); + } - // We can only work with an untyped source if we're doing a roundtrip, - // so we have all serialization details available. - if (hasXmlSource) - { - _roundtripMode = true; + _roundtripMode = true; #pragma warning disable CS0618 // Type or member is obsolete - return buildInternal(source.ToTypedElement()); + return buildInternal(source.ToTypedElement()); #pragma warning restore CS0618 // Type or member is obsolete - } - else - { - throw Error.NotSupported($"The {nameof(FhirXmlBuilder)} will only work correctly on an untyped " + - $"source if the source is a {nameof(FhirXmlNode)}."); - } - } + } - public XDocument buildInternal(ITypedElement source) - { - var dest = new XDocument(); + private XDocument buildInternal(ITypedElement source) + { + var dest = new XDocument(); - if (source is IExceptionSource) + if (source is IExceptionSource) + { + using (source.Catch((o, a) => ExceptionHandler.NotifyOrThrow(o, a))) { - using (source.Catch((o, a) => ExceptionHandler.NotifyOrThrow(o, a))) - { - build(source, dest); - } - } - else build(source, dest); - - return dest; + } } + else + build(source, dest); + + return dest; + } + + internal bool MustSerializeMember(ITypedElement source, out IElementDefinitionSummary info) + { + info = source.Definition; - internal bool MustSerializeMember(ITypedElement source, out IElementDefinitionSummary info) + if (info == null && !_roundtripMode) { - info = source.Definition; + var message = $"Element '{source.Location}' is missing type information."; - if (info == null && !_roundtripMode) - { - var message = $"Element '{source.Location}' is missing type information."; - - if (_settings.IgnoreUnknownElements) - { - ExceptionHandler.NotifyOrThrow(source, ExceptionNotification.Warning( - new MissingTypeInformationException(message))); - } - else - { - ExceptionHandler.NotifyOrThrow(source, ExceptionNotification.Error( - new MissingTypeInformationException(message))); - } - - return false; - } + ExceptionHandler.NotifyOrThrow(source, ExceptionNotification.Warning( + new MissingTypeInformationException(message))); - return true; + return false; } - private void build(ITypedElement source, XContainer parent) - { - var xmlDetails = source.GetXmlSerializationDetails(); - var sourceComments = (source as IAnnotated)?.Annotation(); + return true; + } - if (!MustSerializeMember(source, out var serializationInfo)) return; - bool hasTypeInfo = serializationInfo != null; + private void build(ITypedElement source, XContainer parent) + { + var xmlDetails = source.GetXmlSerializationDetails(); + var sourceComments = (source as IAnnotated)?.Annotation(); - var value = source.Value != null ? - PrimitiveTypeConverter.ConvertTo(source.Value) : null; + if (!MustSerializeMember(source, out var serializationInfo)) return; - if (_settings.TrimWhitespaces) - { - value = value?.Trim(); - } + var value = source.Value != null ? + PrimitiveTypeConverter.ConvertTo(source.Value) : null; - // xhtml children require special treament: - // - They don't use an xml "value" attribute to represent the value, instead their Value is inserted verbatim into the parent - // - They cannot have child nodes - the "Value" on the node contains all children as raw xml text - var isXhtml = source.InstanceType == "xhtml" || - serializationInfo?.Representation == XmlRepresentation.XHtml || - xmlDetails?.Namespace?.GetName("div") == XmlNs.XHTMLDIV; + // A little note about trimming and whitespaces. The spec says: + // "Implementers SHOULD trim leading and trailing whitespace before writing and SHOULD trim leading + // and trailing whitespace when reading attribute values (for XML schema conformance)" + value = value?.Trim(); - if (isXhtml && !String.IsNullOrWhiteSpace(value)) - { - // The value *should* be valid xhtml - however if people just provide a plain - // string, lets add a
around it. - if (!value.TrimStart().StartsWith("{value}
"; - - var sanitized = SerializationUtil.SanitizeXml(value); - XElement xe = XElement.Parse(sanitized); - - // The div should be in the XHTMLNS namespace, correct if it - // is not the case. - xe.Name = XmlNs.XHTMLNS + xe.Name.LocalName; - parent.Add(xe); - - //using (var divWriter = parent.CreateWriter()) - //using (var nodeReader = SerializationUtil.XmlReaderFromXmlText(value)) - // divWriter.WriteNode(nodeReader, false); - return; - } + // xhtml children require special treament: + // - They don't use an xml "value" attribute to represent the value, instead their Value is inserted verbatim into the parent + // - They cannot have child nodes - the "Value" on the node contains all children as raw xml text + var isXhtml = source.InstanceType == "xhtml" || + serializationInfo?.Representation == XmlRepresentation.XHtml || + xmlDetails?.Namespace?.GetName("div") == XmlNs.XHTMLDIV; - var usesAttribute = serializationInfo?.Representation == XmlRepresentation.XmlAttr || - (xmlDetails?.NodeType == XmlNodeType.Attribute); - var ns = serializationInfo?.NonDefaultNamespace ?? - xmlDetails?.Namespace.NamespaceName ?? - (usesAttribute ? "" : XmlNs.FHIR); - bool atRoot = parent is XDocument; - var localName = serializationInfo?.IsChoiceElement == true ? - source.Name + source.InstanceType.Capitalize() : source.Name; - - // If the node is represented by an attribute (e.g. an "id" child), write - // an attribute with the child's name + the child's Value into the parent - if (usesAttribute && !String.IsNullOrWhiteSpace(value) && !atRoot) - { - parent.Add(new XAttribute(XName.Get(localName, ns), value)); - return; - } - // else: fall through - value will be serialized as an element + if (isXhtml && !String.IsNullOrWhiteSpace(value)) + { + // The value *should* be valid xhtml - however if people just provide a plain + // string, lets add a
around it. + if (!value.TrimStart().StartsWith("{value}
"; + + var sanitized = SerializationUtil.SanitizeXml(value); + XElement xe = XElement.Parse(sanitized); + + // The div should be in the XHTMLNS namespace, correct if it + // is not the case. + xe.Name = XmlNs.XHTMLNS + xe.Name.LocalName; + parent.Add(xe); + + //using (var divWriter = parent.CreateWriter()) + //using (var nodeReader = SerializationUtil.XmlReaderFromXmlText(value)) + // divWriter.WriteNode(nodeReader, false); + return; + } - var me = new XElement(XName.Get(localName, ns)); + var usesAttribute = serializationInfo?.Representation == XmlRepresentation.XmlAttr || + (xmlDetails?.NodeType == XmlNodeType.Attribute); + var ns = serializationInfo?.NonDefaultNamespace ?? + xmlDetails?.Namespace?.NamespaceName ?? + (usesAttribute ? "" : XmlNs.FHIR); + bool atRoot = parent is XDocument; + var localName = serializationInfo?.IsChoiceElement == true ? + source.Name + source.InstanceType.Capitalize() : source.Name; + + // If the node is represented by an attribute (e.g. an "id" child), write + // an attribute with the child's name + the child's Value into the parent + if (usesAttribute && !String.IsNullOrWhiteSpace(value) && !atRoot) + { + parent.Add(new XAttribute(XName.Get(localName, ns), value)); + return; + } + // else: fall through - value will be serialized as an element - if (xmlDetails?.SchemaLocation != null) - me.Add(new XAttribute(XmlNs.XSCHEMALOCATION, xmlDetails.SchemaLocation)); + var me = new XElement(XName.Get(localName, ns)); - // If the node has a value, add the standard FHIR value attribute - if (value != null) - me.Add(new XAttribute("value", value)); + if (xmlDetails?.SchemaLocation != null) + me.Add(new XAttribute(XmlNs.XSCHEMALOCATION, xmlDetails.SchemaLocation)); - // If this needs to be serialized as a contained resource, do so - var containedResourceType = atRoot ? null : - (serializationInfo?.IsResource == true ? - source.InstanceType : - source.Annotation()?.ResourceType); + // If the node has a value, add the standard FHIR value attribute + if (value != null) + me.Add(new XAttribute("value", value)); - XElement containedResource = null; - if (containedResourceType != null) - containedResource = new XElement(XName.Get(containedResourceType, ns)); + // If this needs to be serialized as a contained resource, do so + var containedResourceType = atRoot ? null : + (serializationInfo?.IsResource == true ? + source.InstanceType : + source.Annotation()?.ResourceType); - var childParent = containedResource ?? me; + XElement containedResource = null; + if (containedResourceType != null) + containedResource = new XElement(XName.Get(containedResourceType, ns)); - // Now, do the same for the children - // xml requires a certain order, so let's make sure we serialize in the right order - var orderedChildren = source.Children().OrderBy(c => c.Definition?.Order ?? 0); + var childParent = containedResource ?? me; - foreach (var child in orderedChildren) - build(child, childParent); + // Now, do the same for the children + // xml requires a certain order, so let's make sure we serialize in the right order + var orderedChildren = source.Children().OrderBy(c => c.Definition?.Order ?? 0); - if (serializationInfo?.Representation == XmlRepresentation.XmlText || xmlDetails?.NodeText != null) - { - childParent.Add(new XText(value ?? xmlDetails.NodeText)); - } + foreach (var child in orderedChildren) + build(child, childParent); - if (sourceComments?.ClosingComments != null) - writeComments(sourceComments.ClosingComments, me); + if (serializationInfo?.Representation == XmlRepresentation.XmlText || xmlDetails?.NodeText != null) + { + childParent.Add(new XText(value ?? xmlDetails.NodeText)); + } - // Only really add this contained resource to me when it has contents - if (containedResource != null && (containedResource.HasAttributes || containedResource.HasElements)) - me.Add(containedResource); + if (sourceComments?.ClosingComments != null) + writeComments(sourceComments.ClosingComments, me); - // Only add myself to my parent if I have content, or I am the root - if (value != null || me.HasElements || me.HasAttributes || atRoot) - { - if (sourceComments?.CommentsBefore != null) - writeComments(sourceComments.CommentsBefore, parent); + // Only really add this contained resource to me when it has contents + if (containedResource != null && (containedResource.HasAttributes || containedResource.HasElements)) + me.Add(containedResource); - parent.Add(me); - } + // Only add myself to my parent if I have content, or I am the root + if (value != null || me.HasElements || me.HasAttributes || atRoot) + { + if (sourceComments?.CommentsBefore != null) + writeComments(sourceComments.CommentsBefore, parent); - if (atRoot && parent.Elements().Any() && sourceComments?.DocumentEndComments != null) - writeComments(sourceComments.DocumentEndComments, parent); + parent.Add(me); } - private void writeComments(string[] comments, XContainer parent) + if (atRoot && parent.Elements().Any() && sourceComments?.DocumentEndComments != null) + writeComments(sourceComments.DocumentEndComments, parent); + } + + private void writeComments(string[] comments, XContainer parent) + { + if (comments?.Any() == true) { - if (comments?.Any() == true) - { - foreach (var comment in comments) - parent.Add(new XComment(comment)); - } + foreach (var comment in comments) + parent.Add(new XComment(comment)); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilderExtensions.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilderExtensions.cs index 55d8855612..4789df431d 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilderExtensions.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilderExtensions.cs @@ -12,89 +12,135 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using PocoNode = Hl7.Fhir.ElementModel.PocoNode; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +public static class FhirXmlBuilderExtensions { - public static class FhirXmlBuilderExtensions + /// + /// Serializes an instance into FHIR Xml. + /// + /// The instance to serialize. + /// The to write the serialized data to. + /// Since has no type information, this function will throw unless + /// the originated from parsing using . + public static void WriteTo(this ISourceNode source, XmlWriter writer) => + new FhirXmlBuilder().Build(source).writeTo(writer); + + /// + public static async Task WriteToAsync(this ISourceNode source, XmlWriter destination) => + await new FhirXmlBuilder().Build(source).writeToAsync(destination).ConfigureAwait(false); + + /// + /// Serializes an instance to FHIR Xml. + /// + /// The instance to serialize. + /// The to write the serialized data to. + public static void WriteTo(this ITypedElement source, XmlWriter writer) => + new FhirXmlBuilder().Build(source).writeTo(writer); + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task WriteToAsync(this ITypedElement source, XmlWriter destination) => + await new FhirXmlBuilder().Build(source).writeToAsync(destination).ConfigureAwait(false); + + /// + /// Serializes an instance into a . + /// + /// The instance to serialize. + /// Since has no type information, this function will throw unless + /// the originated from parsing using . + public static XDocument ToXDocument(this ISourceNode source) => + new FhirXmlBuilder().Build(source); + + /// + /// Serializes an instance into a + /// + /// The instance to serialize. + public static XDocument ToXDocument(this ITypedElement source) => + new FhirXmlBuilder().Build(source); + + /// + /// Serializes an instance into a FHIR Xml string. + /// + /// The instance to serialize. + /// Formats and indents the serialized Xml. + /// Since has no type information, this function will throw unless + /// the originated from parsing using . + public static string ToXml(this ISourceNode source, bool pretty = false) + => SerializationUtil.WriteXmlToString(source.WriteTo, pretty); + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task ToXmlAsync(this ISourceNode source, bool pretty = false) + => await SerializationUtil.WriteXmlToStringAsync(source.WriteToAsync, pretty).ConfigureAwait(false); + + /// + /// Serializes an instance into a FHIR Xml string. + /// + /// The instance to serialize. + /// Formats and indents the serialized Xml. + public static string ToXml(this ITypedElement source, bool pretty = false) + { + if (source is not PocoNode node) + return SerializationUtil.WriteXmlToString(source.WriteTo, pretty); + + return serializePocoNode(node, pretty); + } + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task ToXmlAsync(this ITypedElement source, bool pretty = false) { - /// - private static void writeTo(this XDocument doc, XmlWriter destination) - { - if (doc.Root != null) - doc.WriteTo(destination); - - destination.Flush(); - } - - private static async Task writeToAsync(this XDocument doc, XmlWriter destination) - { - if (doc.Root != null) - doc.WriteTo(destination); - - await destination.FlushAsync().ConfigureAwait(false); - } - - /// - public static void WriteTo(this ISourceNode source, XmlWriter destination, FhirXmlSerializationSettings settings = null) => - new FhirXmlBuilder(settings).Build(source).writeTo(destination); - - public static async Task WriteToAsync(this ISourceNode source, XmlWriter destination, FhirXmlSerializationSettings settings = null) => - await new FhirXmlBuilder(settings).Build(source).writeToAsync(destination).ConfigureAwait(false); - - /// - public static void WriteTo(this ITypedElement source, XmlWriter destination, FhirXmlSerializationSettings settings = null) => - new FhirXmlBuilder(settings).Build(source).writeTo(destination); - - public static async Task WriteToAsync(this ITypedElement source, XmlWriter destination, FhirXmlSerializationSettings settings = null) => - await new FhirXmlBuilder(settings).Build(source).writeToAsync(destination).ConfigureAwait(false); - - public static XDocument ToXDocument(this ISourceNode source, FhirXmlSerializationSettings settings = null) => - new FhirXmlBuilder(settings).Build(source); - - public static XDocument ToXDocument(this ITypedElement source, FhirXmlSerializationSettings settings = null) => - new FhirXmlBuilder(settings).Build(source); - - /// - public static string ToXml(this ISourceNode source, FhirXmlSerializationSettings settings = null) - => SerializationUtil.WriteXmlToString(writer => source.WriteTo(writer, settings), settings?.Pretty ?? false, settings?.AppendNewLine ?? false); - - public static async Task ToXmlAsync(this ISourceNode source, FhirXmlSerializationSettings settings = null) - => await SerializationUtil.WriteXmlToStringAsync(async writer => await source.WriteToAsync(writer, settings).ConfigureAwait(false), settings?.Pretty ?? false, settings?.AppendNewLine ?? false).ConfigureAwait(false); - - /// - [TemporarilyChanged] // This works. Remove this attribute after writing new extensions on the pocos - public static string ToXml(this ITypedElement source, FhirXmlSerializationSettings settings = null) - { - if (source is not PocoNode {Poco: {} b} node) - return SerializationUtil.WriteXmlToString(source, (s,w) => s.WriteTo(w, settings), - settings?.Pretty ?? false, settings?.AppendNewLine ?? false); - - var serializer = new BaseFhirXmlPocoSerializer(node.FindInspector()?.FhirRelease ?? ModelInspector.ForType(b.GetType()).FhirRelease); - return serializer.SerializeToString(b); - } - - [TemporarilyChanged] // This works. Remove this attribute after writing new extensions on the pocos - public static async Task ToXmlAsync(this ITypedElement source, FhirXmlSerializationSettings settings = null) - { - if (source is not PocoNode {Poco: {} b} node) - return await SerializationUtil.WriteXmlToStringAsync(async writer => await source.WriteToAsync(writer, settings).ConfigureAwait(false), settings?.Pretty ?? false, - settings?.AppendNewLine ?? false).ConfigureAwait(false); - - var serializer = new BaseFhirXmlPocoSerializer(node.FindInspector()?.FhirRelease ?? ModelInspector.ForType(b.GetType()).FhirRelease); - return serializer.SerializeToString(b); - } - - /// - public static byte[] ToXmlBytes(this ITypedElement source, FhirXmlSerializationSettings settings = null) - => SerializationUtil.WriteXmlToBytes(writer => source.WriteTo(writer, settings)); - - public static async Task ToXmlBytesAsync(this ITypedElement source, FhirXmlSerializationSettings settings = null) - => await SerializationUtil.WriteXmlToBytesAsync(async writer => await source.WriteToAsync(writer, settings).ConfigureAwait(false)).ConfigureAwait(false); + if (source is not PocoNode node) + return await SerializationUtil.WriteXmlToStringAsync(source.WriteToAsync, pretty).ConfigureAwait(false); + + return serializePocoNode(node, pretty); + } + + private static string serializePocoNode(PocoNode pn, bool pretty) + { + var serializer = new BaseFhirXmlSerializer(pn.FindInspector() ?? ModelInspector.ForType(pn.Poco.GetType())); + + // If we are serializing a subtree of a resource, then if the current node is a datatype or a nested resource, + // we need to pick a name for this root element. + var pickElementName = pn.Poco is not Resource || pn.Parent is not null; + var rootName = pickElementName ? pn.Name : null; + + return serializer.SerializeToString(pn.Poco, pretty, rootName: rootName); + } + + /// + /// Serializes an instance into a FHIR Xml byte array. + /// + /// The instance to serialize. + /// Formats and indents the serialized Xml. + public static byte[] ToXmlBytes(this ITypedElement source, bool pretty = false) + => SerializationUtil.WriteXmlToBytes(source.WriteTo, pretty); + + /// + [Obsolete("Async support will be removed in the next major release, please use the non-async version instead")] + public static async Task ToXmlBytesAsync(this ITypedElement source, bool pretty = false) + => await SerializationUtil.WriteXmlToBytesAsync(source.WriteToAsync, pretty).ConfigureAwait(false); + + private static void writeTo(this XDocument doc, XmlWriter destination) + { + if (doc.Root != null) + doc.WriteTo(destination); + + destination.Flush(); + } + + private static async Task writeToAsync(this XDocument doc, XmlWriter destination) + { + if (doc.Root != null) + await doc.WriteToAsync(destination, CancellationToken.None); + + await destination.FlushAsync().ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlParsingSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlParsingSettings.cs index 78617188e9..5742edc5d9 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlParsingSettings.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirXmlParsingSettings.cs @@ -64,7 +64,7 @@ public void CopyTo(FhirXmlParsingSettings other) /// Creates a new object that is a copy of the current instance. public FhirXmlParsingSettings Clone() => new(this); - /// Creates a new instance with default property values. + /// Creates a new instance with default property values. public static FhirXmlParsingSettings CreateDefault() => new(); } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlSerializationSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlSerializationSettings.cs deleted file mode 100644 index c9c88110bb..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlSerializationSettings.cs +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2018, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE - */ - -using Hl7.Fhir.Utility; -using System; - -namespace Hl7.Fhir.Serialization -{ - /// Configuration settings for the class. - public class FhirXmlSerializationSettings - { - /// - /// When encountering a member without type information, just skip it instead of reporting an error. - /// - public bool IgnoreUnknownElements; - - /// - /// Format the xml output when converted to a string. - /// - public bool Pretty { get; set; } // = false; - - /// - /// Add new line at the end of the xml output when converted to a string. - /// - public bool AppendNewLine { get; set; } // = false; - - /// - /// Trims whitespaces add the beginning and the end of xml value attributes - /// - public bool TrimWhitespaces { get; set; } = true; - - /// Default constructor. Creates a new instance with default property values. - public FhirXmlSerializationSettings() { } - - /// Clone constructor. Generates a new instance initialized from the state of the specified instance. - /// The specified argument is null. - public FhirXmlSerializationSettings(FhirXmlSerializationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - other.CopyTo(this); - } - - /// Copy all configuration settings to another instance. - /// Another instance. - /// The specified argument is null. - public void CopyTo(FhirXmlSerializationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - - other.IgnoreUnknownElements = IgnoreUnknownElements; - other.Pretty = Pretty; - other.AppendNewLine = AppendNewLine; - other.TrimWhitespaces = TrimWhitespaces; - } - - /// Creates a new object that is a copy of the current instance. - public FhirXmlSerializationSettings Clone() => new FhirXmlSerializationSettings(this); - - /// Creates a new instance with default property values. - public static FhirXmlSerializationSettings CreateDefault() => new FhirXmlSerializationSettings(); - } -} diff --git a/src/Hl7.Fhir.Base/Serialization/JsonSerializationExtensions.cs b/src/Hl7.Fhir.Base/Serialization/JsonSerializationExtensions.cs new file mode 100644 index 0000000000..cddc2c527c --- /dev/null +++ b/src/Hl7.Fhir.Base/Serialization/JsonSerializationExtensions.cs @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2018, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Utility; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Text.Json; +using Tasks = System.Threading.Tasks; + +namespace Hl7.Fhir.Serialization; + +public static class JsonSerializationExtensions +{ + /// + /// Serializes the given POCO into a FHIR Json string. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Json. + /// An optional to use to serialize summaries. + public static string SerializeToString(this BaseFhirJsonSerializer ser, Base instance, bool pretty = false, + SerializationFilter? filter = null) => + SerializationUtil.WriteJsonToString(w => ser.Serialize(instance, w, filter), pretty); + + /// + /// Serializes the given POCO into a FHIR Json string. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Json. + /// The kind of summary to use for serialization. Optional . + /// If is , specifies which + /// top-level elements of the resource to serialize. Optional. + /// If is , + /// indicates whether to include mandatory elements in the summary, in addition to those in . Optional. + public static string SerializeToString(this BaseFhirJsonSerializer ser, Base instance, + SummaryType summary, string[]? elements = null, + bool includeMandatoryInElementsSummary = false, + bool pretty = false) => + ser.SerializeToString( + instance, + pretty, + summary.GetSerializationFilter(elements, includeMandatoryInElementsSummary)); + + [Obsolete("The new serializers do not support async serialization, use the synchronous version instead.")] + public static Tasks.Task SerializeToStringAsync(this BaseFhirJsonSerializer ser, Base instance, + SummaryType summary = SummaryType.False, string[]? elements = null, bool includeMandatoryInElementsSummary = false, + bool pretty = false) => + TaskExtensions.FromResult(ser.SerializeToString(instance, summary, elements, includeMandatoryInElementsSummary, pretty)); + + + /// + /// Serializes the given POCO into a FHIR Json byte array. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Json. + /// An optional to use to serialize summaries. + public static byte[] SerializeToBytes(this BaseFhirJsonSerializer ser, Base instance, bool pretty = false, + SerializationFilter? filter = null) => + SerializationUtil.WriteJsonToBytes(w => ser.Serialize(instance, w, filter), pretty); + + /// + /// Serializes the given POCO into a FHIR Json byte array. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Json. + /// The kind of summary to use for serialization. Optional . + /// If is , specifies which + /// top-level elements of the resource to serialize. Optional. + /// If is , + /// indicates whether to include mandatory elements in the summary, in addition to those in . Optional. + public static byte[] SerializeToBytes(this BaseFhirJsonSerializer ser, Base instance, + SummaryType summary, string[]? elements = null, bool includeMandatoryInElementsSummary = false, + bool pretty = false) => + ser.SerializeToBytes( + instance, + pretty, + summary.GetSerializationFilter(elements, includeMandatoryInElementsSummary)); + + [Obsolete("The new serializers do not support async serialization, use the synchronous version instead.")] + public static Tasks.Task SerializeToBytesAsync(this BaseFhirJsonSerializer ser, Base instance, + SummaryType summary = SummaryType.False, string[]? elements = null, + bool includeMandatoryInElementsSummary = false, + bool pretty = false) => + TaskExtensions.FromResult(ser.SerializeToBytes(instance, summary, elements, includeMandatoryInElementsSummary, pretty)); + + [Obsolete( + "We're phasing out Newtonsoft in favor of System.Text.Json, please use FhirJsonSerializer.Default.Serialize() instead.")] + public static JObject SerializeToDocument(this BaseFhirJsonSerializer ser, Base instance, + SummaryType summary = SummaryType.False, string[]? elements = null, + bool includeMandatoryInElementsSummary = false) + { + var jsonText = ser.SerializeToString(instance, summary, elements, includeMandatoryInElementsSummary); + return JObject.Parse(jsonText); + } + + [Obsolete( + "We're phasing out Newtonsoft in favor of System.Text.Json, please use FhirJsonSerializer.Default.Serialize() instead.")] + public static void Serialize(this BaseFhirJsonSerializer ser, Base instance, JsonWriter writer, + SummaryType summary = SummaryType.False, string[]? elements = null, + bool includeMandatoryInElementsSummary = false) + { + var jsonText = ser.SerializeToString(instance, summary, elements, includeMandatoryInElementsSummary); + writer.WriteRaw(jsonText); + } + + [Obsolete("We're phasing out Newtonsoft in favor of System.Text.Json, please use FhirJsonSerializer.Default.Serialize() instead.")] + public static async Tasks.Task SerializeAsync(this BaseFhirJsonSerializer ser, Base instance, JsonWriter writer, + SummaryType summary = SummaryType.False, string[]? elements = null, bool includeMandatoryInElementsSummary = false) + { + var jsonText = ser.SerializeToString(instance, summary, elements, includeMandatoryInElementsSummary); + await writer.WriteRawAsync(jsonText).ConfigureAwait(false); + } + + /// + /// Serializes the given POCO into Json. + /// + /// The serializer to use. + /// The instance to serialize. + /// The to write the serialized data to. + /// The kind of summary to use for serialization. Optional . + /// If is , specifies which + /// top-level elements of the resource to serialize. Optional. + /// If is , + /// indicates whether to include mandatory elements in the summary, in addition to those in . Optional. + public static void Serialize(this BaseFhirJsonSerializer ser, Base instance, Utf8JsonWriter writer, + SummaryType summary = SummaryType.False, string[]? elements = null, bool includeMandatoryInElementsSummary = false) => + ser.Serialize( + instance, + writer, + summary.GetSerializationFilter(elements, includeMandatoryInElementsSummary)); + + [Obsolete("The new serializers do not support async serialization, use the synchronous version instead.")] + public static Tasks.Task SerializeAsync(this BaseFhirJsonSerializer ser, Base instance, Utf8JsonWriter writer, + SummaryType summary = SummaryType.False, string[]? elements = null, + bool includeMandatoryInElementsSummary = false) + { + ser.Serialize(instance, writer, summary, elements, includeMandatoryInElementsSummary); + return Tasks.Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/JsonSerializerOptionsExtensions.cs b/src/Hl7.Fhir.Base/Serialization/JsonSerializerOptionsExtensions.cs deleted file mode 100644 index 848a26046b..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/JsonSerializerOptionsExtensions.cs +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) 2021, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -#nullable enable - -using Hl7.Fhir.Introspection; -using Hl7.Fhir.Model; -using Hl7.Fhir.Model.CdsHooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; - -namespace Hl7.Fhir.Serialization -{ - /// - /// Utility extension method to initialize the to use the System.Text.Json - /// based (de)serializers. - /// - public static class JsonSerializerOptionsExtensions - { - /// - /// Initialize the options to serialize using the CDS Hooks specific (de)serializers. Note that this also adds the FHIR specific converters, since FHIR is expected in the CDS Hooks messages. - /// -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.Experimental(diagnosticId: "ExperimentalApi")] -#else - [System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] -#endif - public static JsonSerializerOptions ForCdsHooks(this JsonSerializerOptions options, ModelInspector inspector, FhirJsonPocoSerializerSettings? serializerSettings = null, - FhirJsonPocoDeserializerSettings? deserializerSettings = null) => - options.ForFhir(inspector, serializerSettings ?? new FhirJsonPocoSerializerSettings(), deserializerSettings ?? new FhirJsonPocoDeserializerSettings()).addCdsHooks(); - - - /// -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.Experimental(diagnosticId: "ExperimentalApi")] -#else - [System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] -#endif - public static JsonSerializerOptions ForCdsHooks(this JsonSerializerOptions options, Assembly modelAssembly, FhirJsonPocoSerializerSettings? serializerSettings = null, - FhirJsonPocoDeserializerSettings? deserializerSettings = null) => - options.ForFhir(modelAssembly, serializerSettings ?? new FhirJsonPocoSerializerSettings(), deserializerSettings ?? new FhirJsonPocoDeserializerSettings()) - .addCdsHooks(); - - private static JsonSerializerOptions addCdsHooks(this JsonSerializerOptions options) - { - options.TypeInfoResolver = (options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver()).WithAddedModifier(changeCdsHookPropertyNames); - - return options; - - static void changeCdsHookPropertyNames(JsonTypeInfo ti) - { - if (ti.Type.GetCustomAttribute() is null) return; - - foreach (var p in ti.Properties) - p.Name = char.ToLower(p.Name.First()) + p.Name.Substring(1); - } - } - - /// - /// Initialize the options to serialize using the JsonFhirConverter, producing compact output without whitespace. - /// - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, Assembly modelAssembly) => - options.ForFhir(modelAssembly, new(), new()); - - /// - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, Assembly modelAssembly, FhirJsonPocoSerializerSettings serializerSettings) => - options.ForFhir(modelAssembly, serializerSettings, new()); - - /// - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, Assembly modelAssembly, FhirJsonPocoDeserializerSettings deserializerSettings) => - options.ForFhir(modelAssembly, new(), deserializerSettings); - - /// - public static JsonSerializerOptions ForFhir( - this JsonSerializerOptions options, - Assembly modelAssembly, - FhirJsonPocoSerializerSettings serializerSettings, - FhirJsonPocoDeserializerSettings deserializerSettings - ) - { - var converter = new FhirJsonConverterFactory(modelAssembly, serializerSettings, deserializerSettings); - return options.ForFhir(converter); - } - - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, ModelInspector inspector) => - options.ForFhir(inspector, new(), new()); - - /// - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, ModelInspector inspector, FhirJsonPocoSerializerSettings serializerSettings) => - options.ForFhir(inspector, serializerSettings, new()); - - /// - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, ModelInspector inspector, FhirJsonPocoDeserializerSettings deserializerSettings) => - options.ForFhir(inspector, new(), deserializerSettings); - - /// - public static JsonSerializerOptions ForFhir( - this JsonSerializerOptions options, - ModelInspector inspector, - FhirJsonPocoSerializerSettings serializerSettings, - FhirJsonPocoDeserializerSettings deserializerSettings - ) - { - var converter = new FhirJsonConverterFactory(inspector, serializerSettings, deserializerSettings); - return options.ForFhir(converter); - } - - /// - /// Initialize the options to serialize using the JsonFhirConverterFactory, producing compact output without whitespace. - /// - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, FhirJsonConverterFactory converter) - { - options.Converters.Add(converter); - options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; - - return options; - } - - /// - /// Initialize the options to serialize using the JsonFhirConverter, producing compact output without whitespace. - /// - public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, FhirJsonConverter converter) where F : Base - { - options.Converters.Add(converter); - options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; - - return options; - } - - /// - /// Modify the options to use a preset list of errors to ignore by specifying a mode. This can be any member of - /// - /// - /// Modifying the options is always left-associative. This means that defining custom constraints should probably be done AFTER setting the mode. - /// - public static JsonSerializerOptions UsingMode(this JsonSerializerOptions options, DeserializerModes mode) - { - getCustomFactoryFromList(options.Converters).SetMode(mode); - return options; - } - - /// - /// Modify the options to use a custom list of errors to enforce. - /// - /// - /// - Modifying the options is always left-associative. This means that defining custom constraints should probably be done AFTER setting the mode. - /// - This also means that enforcing, then ignoring an error has the opposite behaviour as ignoring it, then enforcing it. - /// - public static JsonSerializerOptions Enforcing(this JsonSerializerOptions options, IEnumerable toEnforce) - { - getCustomFactoryFromList(options.Converters).SetEnforcedErrors(toEnforce); - return options; - } - - /// - /// Modify the options to use a custom list of errors to ignore. - /// - /// - /// - Modifying the options is always left-associative. This means that defining custom constraints should probably be done AFTER setting the mode. - /// - This also means that enforcing, then ignoring an error has the opposite behaviour as ignoring it, then enforcing it. - /// - public static JsonSerializerOptions Ignoring(this JsonSerializerOptions options, IEnumerable toIgnore) - { - getCustomFactoryFromList(options.Converters).SetIgnoredErrors(toIgnore); - return options; - } - - /// - /// Initialize the options to serialize using the JsonFhirConverter, producing compact output without whitespace. - /// - public static JsonSerializerOptions Compact(this JsonSerializerOptions options) - { - options.WriteIndented = false; - return options; - } - - /// - /// Initialize the options to serialize using the JsonFhirConverter, producing pretty output. - /// - public static JsonSerializerOptions Pretty(this JsonSerializerOptions options) - { - options.WriteIndented = true; - return options; - } - - // internal for testing purposes - internal static JsonConverter? FindCustomConverter(this IEnumerable converters) - { - return converters.FirstOrDefault(jsonConverter => jsonConverter.CanConvert(typeof(Resource))); - } - - private static FhirJsonConverterFactory getCustomFactoryFromList(IEnumerable converters) - { - return FindCustomConverter(converters) as FhirJsonConverterFactory ?? throw new NotSupportedException( - "Customizing a FHIR serializer can only be done after it was created. Try calling .ForFhir first"); - } - } - - /// - /// Enumerates the modes with which a deserializer can be configured - /// - public enum DeserializerModes - { - /// - /// Do not ignore any errors (default behaviour for most implementations) - /// - Strict, - - /// - /// An issue is recoverable if all data present in the parsed data could be retrieved and - /// captured in the POCO model, even if the syntax or the data was not fully FHIR compliant. - /// - Recoverable, - - /// - /// An issue is allowable for backwards compatibility if it could be caused because an older parser encounters data coming from a newer - /// FHIR release. This means allowing unknown elements, attributes, codes and types in a choice element. Note that the POCO model cannot capture - /// these newer elements and data, so this means data loss may occur. - /// - BackwardsCompatible, - - /// - /// Ignore all errors. Useful for debugging and/or when you know the data to be parsed is a correct instance. - /// - Ostrich, - } -} - -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/SerializationFilter.cs b/src/Hl7.Fhir.Base/Serialization/SerializationFilter.cs index 5873a2787d..02de140eb7 100644 --- a/src/Hl7.Fhir.Base/Serialization/SerializationFilter.cs +++ b/src/Hl7.Fhir.Base/Serialization/SerializationFilter.cs @@ -10,76 +10,74 @@ using Hl7.Fhir.Introspection; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +/// +/// A filter that instructs the serializers which parts of a tree to serialize. +/// +public abstract class SerializationFilter { /// - /// A filter that instructs the serializers which parts of a tree to serialize. + /// The serializer calls this function when it starts serializing a complex object. /// - public abstract class SerializationFilter - { - /// - /// The serializer calls this function when it starts serializing a complex object. - /// - public abstract void EnterObject(object value, ClassMapping? mapping); + public abstract void EnterObject(object value, ClassMapping? mapping); - /// - /// The serializer calls this function when it needs to serialize the subtree contained in an element. - /// When this function return false, the subtree will not be serialized. - /// - public abstract bool TryEnterMember(string name, object value, PropertyMapping? mapping); + /// + /// The serializer calls this function when it needs to serialize the subtree contained in an element. + /// When this function return false, the subtree will not be serialized. + /// + public abstract bool TryEnterMember(string name, object value, PropertyMapping? mapping); - /// - /// The serializer calls this function when it is done serializing the subtree for an element. - /// - public abstract void LeaveMember(string name, object value, PropertyMapping? mapping); + /// + /// The serializer calls this function when it is done serializing the subtree for an element. + /// + public abstract void LeaveMember(string name, object value, PropertyMapping? mapping); - /// - /// The serializer calls this function when it is done serializing a complex object. - /// - public abstract void LeaveObject(object value, ClassMapping? mapping); + /// + /// The serializer calls this function when it is done serializing a complex object. + /// + public abstract void LeaveObject(object value, ClassMapping? mapping); - /// - /// Construct a new filter that conforms to the `_summary=true` summarized form. - /// - public static SerializationFilter ForSummary() => new BundleFilter(new ElementMetadataFilter() { IncludeInSummary = true }); + /// + /// Construct a new filter that conforms to the `_summary=true` summarized form. + /// + public static SerializationFilter ForSummary() => new BundleFilter(new ElementMetadataFilter() { IncludeInSummary = true }); - /// - /// Construct a new filter that conforms to the `_summary=text` summarized form. - /// - public static SerializationFilter ForText() => new BundleFilter(new TopLevelFilter( - new ElementMetadataFilter() - { - IncludeNames = new[] { "text", "id", "meta" }, - IncludeMandatory = true - })); + /// + /// Construct a new filter that conforms to the `_summary=text` summarized form. + /// + public static SerializationFilter ForText() => new BundleFilter(new TopLevelFilter( + new ElementMetadataFilter() + { + IncludeNames = ["text", "id", "meta"], + IncludeMandatory = true + })); + + public static SerializationFilter ForCount() => new TopLevelFilter( + new ElementMetadataFilter + { + //IncludeMandatory = true, + IncludeNames = ["id", "total", "link", "type"] + }); + + /// + /// Construct a new filter that conforms to the `_summary=data` summarized form. + /// + public static SerializationFilter ForData() => new BundleFilter(new TopLevelFilter( + new ElementMetadataFilter + { + IncludeNames = ["text"], + Invert = true + })); - public static SerializationFilter ForCount() => new BundleFilter(new TopLevelFilter( + /// + /// Construct a new filter that conforms to the `_elements=...` summarized form. + /// + public static SerializationFilter ForElements(string[] elements, bool includeMandatory = true) => + new BundleFilter(new TopLevelFilter( new ElementMetadataFilter() { - IncludeMandatory = true, - IncludeNames = new[] { "id", "total", "link" } + IncludeNames = elements, + IncludeMandatory = includeMandatory })); - - /// - /// Construct a new filter that conforms to the `_summary=data` summarized form. - /// - public static SerializationFilter ForData() => new BundleFilter(new TopLevelFilter( - new ElementMetadataFilter() - { - IncludeNames = new[] { "text" }, - Invert = true - })); - - /// - /// Construct a new filter that conforms to the `_elements=...` summarized form. - /// - public static SerializationFilter ForElements(string[] elements) => new BundleFilter(new TopLevelFilter( - new ElementMetadataFilter() - { - IncludeNames = elements, - IncludeMandatory = true - })); - } -} - -#nullable restore +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/SerializerSettings.cs b/src/Hl7.Fhir.Base/Serialization/SerializerSettings.cs deleted file mode 100644 index 23e8fdf072..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/SerializerSettings.cs +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2016, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - - -using Hl7.Fhir.Utility; -using System; - -namespace Hl7.Fhir.Serialization -{ - public class SerializerSettings - { - /// - /// Format the serialized xml or json output. - /// - public bool Pretty { get; set; } // = false; - - /// - /// Add new line at the end of the serialized xml or json output. - /// - public bool AppendNewLine { get; set; } // = false; - - /// - /// Trim whitespaces at the beginning and end of xml attribute value - /// - public bool TrimWhiteSpacesInXml { get; set; } = true; - - /// - /// Include mandatory elements when serializing a subset of chosen elements (_elements). - /// - public bool IncludeMandatoryInElementsSummary { get; set; } = false; - - /// Default constructor. Creates a new instance with default property values. - public SerializerSettings() { } - - /// Clone constructor. Generates a new instance initialized from the state of the specified instance. - /// The specified argument is null. - public SerializerSettings(SerializerSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - other.CopyTo(this); - } - - /// Copy all configuration settings to another instance. - /// Another instance. - /// The specified argument is null. - public void CopyTo(SerializerSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - - other.Pretty = Pretty; - other.AppendNewLine = AppendNewLine; - other.TrimWhiteSpacesInXml = TrimWhiteSpacesInXml; - other.IncludeMandatoryInElementsSummary = IncludeMandatoryInElementsSummary; - } - - /// Creates a new object that is a copy of the current instance. - public SerializerSettings Clone() => new SerializerSettings(this); - - /// Creates a new instance with default property values. - public static SerializerSettings CreateDefault() => new SerializerSettings(); - } -} diff --git a/src/Hl7.Fhir.Base/Serialization/XmlReaderExtensions.cs b/src/Hl7.Fhir.Base/Serialization/XmlReaderExtensions.cs index 88f23d863d..d171599ed0 100644 --- a/src/Hl7.Fhir.Base/Serialization/XmlReaderExtensions.cs +++ b/src/Hl7.Fhir.Base/Serialization/XmlReaderExtensions.cs @@ -1,87 +1,93 @@ -#nullable enable +/* + * Copyright (c) 2018, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE + */ + +#nullable enable using Hl7.Fhir.Utility; using System.Xml; using ERR = Hl7.Fhir.Serialization.FhirXmlException; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +internal static class XmlReaderExtensions { - internal static class XmlReaderExtensions + internal static string GenerateLocationMessage(this XmlReader reader) { - internal static string GenerateLocationMessage(this XmlReader reader) - { - return GenerateLocationMessage(reader, out var _, out var _); - } + return GenerateLocationMessage(reader, out var _, out var _); + } - internal static string GenerateLocationMessage(this XmlReader reader, out long lineNumber, out long position) - { - (lineNumber, position) = GenerateLineInfo(reader); - return GenerateLocationMessage(lineNumber, position); - } + internal static string GenerateLocationMessage(this XmlReader reader, out long lineNumber, out long position) + { + (lineNumber, position) = GenerateLineInfo(reader); + return GenerateLocationMessage(lineNumber, position); + } - internal static string GenerateLocationMessage(long lineNumber, long position) + internal static string GenerateLocationMessage(long lineNumber, long position) + { + return $"At line {lineNumber}, position {position}."; + } + + internal static (int lineNumber, int position) GenerateLineInfo(this XmlReader reader) + { + IXmlLineInfo xmlInfo = (IXmlLineInfo)reader; + return (xmlInfo.LineNumber, xmlInfo.LinePosition); + } + + internal static bool ReadToContent(this XmlReader reader, FhirXmlPocoDeserializerState state) + { + if (reader.Read()) { - return $"At line {lineNumber}, position {position}."; + while (reader.ShouldSkipNodeType(state)) + { + reader.Skip(); + } + return true; } + return false; + } + internal static bool ShouldSkipNodeType(this XmlReader reader, FhirXmlPocoDeserializerState state) + { + var nodeType = reader.NodeType; - internal static (int lineNumber, int position) GenerateLineInfo(this XmlReader reader) + if (nodeType == XmlNodeType.Comment || nodeType == XmlNodeType.Whitespace || nodeType == XmlNodeType.XmlDeclaration || nodeType == XmlNodeType.SignificantWhitespace) + return true; + else if (nodeType == XmlNodeType.CDATA || nodeType == XmlNodeType.ProcessingInstruction || nodeType == XmlNodeType.DocumentType || nodeType == XmlNodeType.EntityReference) { - IXmlLineInfo xmlInfo = (IXmlLineInfo)reader; - return (xmlInfo.LineNumber, xmlInfo.LinePosition); + state.Errors.Add(ERR.UNALLOWED_NODE_TYPE(reader, state.Path.GetInstancePath(), nodeType.GetLiteral())); + return true; } - - internal static bool ReadToContent(this XmlReader reader, FhirXmlPocoDeserializerState state) + else { - if (reader.Read()) - { - while (reader.ShouldSkipNodeType(state)) - { - reader.Skip(); - } - return true; - } return false; } - internal static bool ShouldSkipNodeType(this XmlReader reader, FhirXmlPocoDeserializerState state) - { - var nodeType = reader.NodeType; + } - if (nodeType == XmlNodeType.Comment || nodeType == XmlNodeType.Whitespace || nodeType == XmlNodeType.XmlDeclaration || nodeType == XmlNodeType.SignificantWhitespace) - return true; - else if (nodeType == XmlNodeType.CDATA || nodeType == XmlNodeType.ProcessingInstruction || nodeType == XmlNodeType.DocumentType || nodeType == XmlNodeType.EntityReference) - { - state.Errors.Add(ERR.UNALLOWED_NODE_TYPE(reader, state.Path.GetInstancePath(), nodeType.GetLiteral())); - return true; - } - else - { - return false; - } - } + internal static bool HasValueAttributeOrChildren(this XmlReader reader) + { + return reader.GetAttribute("value") != null + || reader.HasChildren(); + } - internal static bool HasValueAttributeOrChildren(this XmlReader reader) + internal static bool HasChildren(this XmlReader reader) + { + var subtree = reader.ReadSubtree(); + if (subtree != null) { - return reader.GetAttribute("value") != null - || reader.HasChildren(); + subtree.Read(); + var parentDepth = subtree.Depth; + return subtree.Read() + && (subtree.Depth != parentDepth); } - - internal static bool HasChildren(this XmlReader reader) + else { - var subtree = reader.ReadSubtree(); - if (subtree != null) - { - subtree.Read(); - var parentDepth = subtree.Depth; - return subtree.Read() - && (subtree.Depth != parentDepth); - } - else - { - return false; - } - + return false; } + } -} -#nullable restore +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/XmlSerializationExtensions.cs b/src/Hl7.Fhir.Base/Serialization/XmlSerializationExtensions.cs new file mode 100644 index 0000000000..659f414c10 --- /dev/null +++ b/src/Hl7.Fhir.Base/Serialization/XmlSerializationExtensions.cs @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Utility; +using System; +using System.Xml; +using System.Xml.Linq; +using Tasks = System.Threading.Tasks; + +namespace Hl7.Fhir.Serialization; + +public static class XmlSerializationExtensions +{ + /// + /// Serializes the given POCO into a FHIR Xml string. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Xml. + /// An optional to use to serialize summaries. + /// When serializing subtrees, the root element is named after the type of the instance. + /// If necessary, use this parameter to override the name of the root element. + public static string SerializeToString(this BaseFhirXmlSerializer ser, Base instance, bool pretty = false, + SerializationFilter? filter = null, string? rootName = null) => + SerializationUtil.WriteXmlToString(w => ser.Serialize(instance, w, filter, rootName), pretty); + + /// + /// Serializes the given POCO into a FHIR Xml string. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Xml. + /// The kind of summary to use for serialization. Optional . + /// If is , specifies which + /// top-level elements of the resource to serialize. Optional. + /// If is , + /// indicates whether to include mandatory elements in the summary, in addition to those in . Optional. + /// When serializing subtrees, the root element is named after the type of the instance. + /// If necessary, use this parameter to override the name of the root element. + public static string SerializeToString(this BaseFhirXmlSerializer ser, Base instance, + SummaryType summary, string[]? elements = null, + bool includeMandatoryInElementsSummary = false, + string? rootName = null, + bool pretty = false) => + ser.SerializeToString( + instance, + pretty, + summary.GetSerializationFilter(elements, includeMandatoryInElementsSummary), + rootName); + + [Obsolete("The new serializers do not support async serialization, use the synchronous version instead.")] + public static Tasks.Task SerializeToStringAsync(this BaseFhirXmlSerializer ser, Base instance, + SummaryType summary = SummaryType.False, string[]? elements = null, bool includeMandatoryInElementsSummary = false, + string? rootName = null, + bool pretty = false) => + TaskExtensions.FromResult(ser.SerializeToString(instance, summary, elements, includeMandatoryInElementsSummary, rootName, pretty)); + + + /// + /// Serializes the given POCO into a FHIR Xml byte array. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Xml. + /// An optional to use to serialize summaries. + /// When serializing subtrees, the root element is named after the type of the instance. + /// If necessary, use this parameter to override the name of the root element. + public static byte[] SerializeToBytes(this BaseFhirXmlSerializer ser, Base instance, bool pretty = false, + SerializationFilter? filter = null, string? rootName = null) => + SerializationUtil.WriteXmlToBytes(w => ser.Serialize(instance, w, filter, rootName), pretty); + + /// + /// Serializes the given POCO into a FHIR Xml byte array. + /// + /// The serializer to use. + /// The instance to serialize. + /// Formats and indents the serialized Xml. + /// The kind of summary to use for serialization. Optional . + /// If is , specifies which + /// top-level elements of the resource to serialize. Optional. + /// If is , + /// indicates whether to include mandatory elements in the summary, in addition to those in . Optional. + /// When serializing subtrees, the root element is named after the type of the instance. + /// If necessary, use this parameter to override the name of the root element. + public static byte[] SerializeToBytes(this BaseFhirXmlSerializer ser, Base instance, + SummaryType summary, string[]? elements = null, bool includeMandatoryInElementsSummary = false, + string? rootName = null, + bool pretty = false) => + ser.SerializeToBytes( + instance, + pretty, + summary.GetSerializationFilter(elements, includeMandatoryInElementsSummary), + rootName); + + [Obsolete("The new serializers do not support async serialization, use the synchronous version instead.")] + public static Tasks.Task SerializeToBytesAsync(this BaseFhirXmlSerializer ser, Base instance, + SummaryType summary = SummaryType.False, string[]? elements = null, bool includeMandatoryInElementsSummary = false, + string? rootName = null, + bool pretty = false) => + TaskExtensions.FromResult(ser.SerializeToBytes(instance, summary, elements, includeMandatoryInElementsSummary, rootName, pretty)); + + /// + /// Serializes the given POCO into a FHIR . + /// + /// The serializer to use. + /// The instance to serialize. + /// The kind of summary to use for serialization. Optional . + /// If is , specifies which + /// top-level elements of the resource to serialize. Optional. + /// If is , + /// indicates whether to include mandatory elements in the summary, in addition to those in . Optional. + /// When serializing subtrees, the root element is named after the type of the instance. + /// If necessary, use this parameter to override the name of the root element. + public static XDocument SerializeToDocument(this BaseFhirXmlSerializer ser, Base instance, + SummaryType summary = SummaryType.False, string[]? elements = null, bool includeMandatoryInElementsSummary = false, + string? rootName = null) + { + var result = new XDocument(); + using var writer = result.CreateWriter(); + ser.Serialize(instance, writer, summary, elements, includeMandatoryInElementsSummary, rootName); + writer.Flush(); + + return result; + } + + /// + /// Serializes the given POCO into Xml. + /// + /// The serializer to use. + /// The instance to serialize. + /// The to write the serialized data to. + /// The kind of summary to use for serialization. Optional . + /// If is , specifies which + /// top-level elements of the resource to serialize. Optional. + /// If is , + /// indicates whether to include mandatory elements in the summary, in addition to those in . Optional. + /// When serializing subtrees, the root element is named after the type of the instance. + /// If necessary, use this parameter to override the name of the root element. + public static void Serialize(this BaseFhirXmlSerializer ser, Base instance, XmlWriter writer, + SummaryType summary = SummaryType.False, string[]? elements = null, bool includeMandatoryInElementsSummary = false, + string? rootName = null) => + ser.Serialize( + instance, + writer, + summary.GetSerializationFilter(elements, includeMandatoryInElementsSummary), + rootName); + + [Obsolete("The new serializers do not support async serialization, use the synchronous version instead.")] + public static Tasks.Task SerializeAsync(this BaseFhirXmlSerializer ser, Base instance, XmlWriter writer, + SummaryType summary = SummaryType.False, string[]? elements = null, + bool includeMandatoryInElementsSummary = false, + string? rootName = null) + { + ser.Serialize(instance, writer, summary, elements, includeMandatoryInElementsSummary, rootName); + return Tasks.Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/engine/ElementModelSerializationEngine.cs b/src/Hl7.Fhir.Base/Serialization/engine/ElementModelSerializationEngine.cs index 26bf0baa30..7977d389b4 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/ElementModelSerializationEngine.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/ElementModelSerializationEngine.cs @@ -70,9 +70,9 @@ private Resource deserialize(Func deserializer) } } - public string SerializeToXml(Resource instance) => new CommonFhirXmlSerializer(inspector).SerializeToString(instance); + public string SerializeToXml(Resource instance) => new BaseFhirXmlSerializer(inspector).SerializeToString(instance); - public string SerializeToJson(Resource instance) => new CommonFhirJsonSerializer(inspector).SerializeToString(instance); + public string SerializeToJson(Resource instance) => new BaseFhirJsonSerializer(inspector).SerializeToString(instance); internal class ElementModelParserException(FormatException fe) : CodedException(ELEMENTMODEL_PARSER_EXCEPTION, fe.Message, fe) diff --git a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs index 2902cc89db..867c22003b 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs @@ -9,180 +9,159 @@ #nullable enable -using Hl7.Fhir.ElementModel; using Hl7.Fhir.Introspection; using Hl7.Fhir.Utility; using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +/// +/// Factory methods for creating the default implementation of , as used by the +/// FhirClient. +/// +public static class FhirSerializationEngineFactory { /// - /// Factory methods for creating the default implementation of , as used by the - /// FhirClient. + /// A named scope for the factory methods that use the legacy ElementModel-based (de)serializers. /// - public static class FhirSerializationEngineFactory + public static class Legacy { - /// - /// A named scope for the factory methods that use the legacy ElementModel-based (de)serializers. - /// - public static class Legacy + private enum Mode { - private enum Mode - { - Strict, - Permissive, - BackwardsCompatible, - Ostrich - } - - private static PocoBuilderSettings buildPocoBuilderSettings(Mode mode) => new() - { - AllowUnrecognizedEnums = mode is Mode.BackwardsCompatible or Mode.Ostrich, - IgnoreUnknownMembers = mode is Mode.BackwardsCompatible or Mode.Ostrich, - ExceptionHandler = mode is Mode.Ostrich - ? (_, _) => { } - : null - }; - - - private static FhirXmlParsingSettings buildXmlParsingSettings(Mode mode) => new() - { - DisallowSchemaLocation = mode is Mode.Strict, - PermissiveParsing = mode is Mode.Permissive or Mode.Ostrich, - ValidateFhirXhtml = mode is Mode.Strict - }; - - private static FhirJsonParsingSettings buildJsonParsingSettings(Mode mode) => new() - { - AllowJsonComments = mode is not Mode.Strict, - PermissiveParsing = mode is Mode.Permissive or Mode.Ostrich, - ValidateFhirXhtml = mode is Mode.Strict - }; - - /// - /// Create an implementation of which uses the legacy parser and serializer - /// using set to true. - /// - public static IFhirSerializationEngine FromParserSettings(ModelInspector inspector, ParserSettings settings) => - new ElementModelSerializationEngine(inspector, - BaseFhirParser.BuildXmlParsingSettings(settings), - BaseFhirParser.BuildJsonParserSettings(settings), - BaseFhirParser.BuildPocoBuilderSettings(settings)); - - /// - /// Create an implementation of which uses the legacy parser and serializer - /// using set to true. - /// - public static IFhirSerializationEngine Permissive(ModelInspector inspector) => - new ElementModelSerializationEngine(inspector, - buildXmlParsingSettings(Mode.Permissive), - buildJsonParsingSettings(Mode.Permissive), - buildPocoBuilderSettings(Mode.Permissive)); - - /// - /// Create an implementation of which uses the legacy parser and serializer - /// with set to false. - /// - public static IFhirSerializationEngine Strict(ModelInspector inspector) => - new ElementModelSerializationEngine(inspector, - buildXmlParsingSettings(Mode.Strict), - buildJsonParsingSettings(Mode.Strict), - buildPocoBuilderSettings(Mode.Strict)); - - /// - /// Create an implementation of which uses the legacyt parser and serializer - /// and is configured to allow errors that could occur when reading data from newer releases of FHIR. Note that this - /// parser may drop data that cannot be captured in the POCO model, such as new elements in future FHIR releases. - /// - public static IFhirSerializationEngine BackwardsCompatible(ModelInspector inspector) => - new ElementModelSerializationEngine(inspector, - buildXmlParsingSettings(Mode.BackwardsCompatible), - buildJsonParsingSettings(Mode.BackwardsCompatible), - buildPocoBuilderSettings(Mode.BackwardsCompatible)); - - /// - /// Create an implementation of which uses the legacy parser and serializer - /// configured to allow errors and just continue parsing. Note that this may mean data loss. - /// - public static IFhirSerializationEngine Ostrich(ModelInspector inspector) => - new ElementModelSerializationEngine(inspector, - buildXmlParsingSettings(Mode.Ostrich), - buildJsonParsingSettings(Mode.Ostrich), - buildPocoBuilderSettings(Mode.Ostrich)); + Strict, + Permissive, + BackwardsCompatible, + Ostrich } + private static PocoBuilderSettings buildPocoBuilderSettings(Mode mode) => new() + { + AllowUnrecognizedEnums = mode is Mode.BackwardsCompatible or Mode.Ostrich, + IgnoreUnknownMembers = mode is Mode.BackwardsCompatible or Mode.Ostrich, + ExceptionHandler = mode is Mode.Ostrich + ? (_, _) => { } + : null + }; + - /// - /// Create an implementation of configured to flag all parsing errors, - /// which uses the new Poco-based parser and serializer. - /// - public static IFhirSerializationEngine Strict(ModelInspector inspector, FhirJsonPocoSerializerSettings? serializerSettings = null, - FhirJsonPocoDeserializerSettings? deserializerSettings = null) => - new PocoSerializationEngine(inspector, jsonDeserializerSettings: deserializerSettings, jsonSerializerSettings: serializerSettings); + private static FhirXmlParsingSettings buildXmlParsingSettings(Mode mode) => new() + { + DisallowSchemaLocation = mode is Mode.Strict, + PermissiveParsing = mode is Mode.Permissive or Mode.Ostrich, + ValidateFhirXhtml = mode is Mode.Strict + }; + + private static FhirJsonParsingSettings buildJsonParsingSettings(Mode mode) => new() + { + AllowJsonComments = mode is not Mode.Strict, + PermissiveParsing = mode is Mode.Permissive or Mode.Ostrich, + ValidateFhirXhtml = mode is Mode.Strict + }; /// - /// Create an implementation of configured to ignore recoverable errors, - /// which uses the new Poco-based parser and serializer. + /// Create an implementation of which uses the legacy parser and serializer + /// using set to true. /// - public static IFhirSerializationEngine Recoverable(ModelInspector inspector, FhirJsonPocoSerializerSettings? serializerSettings = null, - FhirJsonPocoDeserializerSettings? deserializerSettings = null) => - new PocoSerializationEngine(inspector, FilterPredicateExtensions.IsRecoverableIssue, deserializerSettings, serializerSettings); + public static IFhirSerializationEngine FromParserSettings(ModelInspector inspector, ParserSettings settings) => + new ElementModelSerializationEngine(inspector, + BaseFhirParser.BuildXmlParsingSettings(settings), + BaseFhirParser.BuildJsonParserSettings(settings), + BaseFhirParser.BuildPocoBuilderSettings(settings)); /// - /// Create an implementation of which uses the new Poco-based parser and - /// serializers and is configured to allow errors that could occur when reading data from newer releases of FHIR. - /// Note that this parser may drop data that cannot be captured in the POCO model, such as new elements in future - /// FHIR releases. + /// Create an implementation of which uses the legacy parser and serializer + /// using set to true. /// - public static IFhirSerializationEngine BackwardsCompatible(ModelInspector inspector, FhirJsonPocoSerializerSettings? serializerSettings = null, - FhirJsonPocoDeserializerSettings? deserializerSettings = null) => - new PocoSerializationEngine(inspector, FilterPredicateExtensions.IsBackwardsCompatibilityIssue, deserializerSettings, serializerSettings); + public static IFhirSerializationEngine Permissive(ModelInspector inspector) => + new ElementModelSerializationEngine(inspector, + buildXmlParsingSettings(Mode.Permissive), + buildJsonParsingSettings(Mode.Permissive), + buildPocoBuilderSettings(Mode.Permissive)); /// - /// Create an implementation of configured to allow errors - /// and just continue parsing. Note that this may mean data loss. + /// Create an implementation of which uses the legacy parser and serializer + /// with set to false. /// - public static IFhirSerializationEngine Ostrich(ModelInspector inspector, FhirJsonPocoSerializerSettings? serializerSettings = null, - FhirJsonPocoDeserializerSettings? deserializerSettings = null) => - new PocoSerializationEngine( - inspector, - _ => true, - (deserializerSettings ?? new FhirJsonPocoDeserializerSettings()) with {Validator = null}, - xmlSettings: new FhirXmlPocoDeserializerSettings {Validator = null}); + public static IFhirSerializationEngine Strict(ModelInspector inspector) => + new ElementModelSerializationEngine(inspector, + buildXmlParsingSettings(Mode.Strict), + buildJsonParsingSettings(Mode.Strict), + buildPocoBuilderSettings(Mode.Strict)); /// - /// Create an implementation of which allows for manual configuration - /// of most behaviour. See parameters for more information. + /// Create an implementation of which uses the legacyt parser and serializer + /// and is configured to allow errors that could occur when reading data from newer releases of FHIR. Note that this + /// parser may drop data that cannot be captured in the POCO model, such as new elements in future FHIR releases. /// - /// - /// A predicate specifying which errors to ignore when parsing - /// The settings to be used by the engine to deserialize JSON sources - /// The settings to be used by the engine to serialize resources to JSON - /// The settings to be used by the engine to deserialize XML sources - /// - public static IFhirSerializationEngine Custom(ModelInspector inspector, Predicate ignoreFilter, - FhirJsonPocoDeserializerSettings? jsonDeserializerSettings = null, - FhirJsonPocoSerializerSettings? jsonSerializerSettings = null, FhirXmlPocoDeserializerSettings? xmlSerializerSettings = null) - { - return new PocoSerializationEngine(inspector, ignoreFilter, jsonDeserializerSettings, jsonSerializerSettings, xmlSerializerSettings); - } + public static IFhirSerializationEngine BackwardsCompatible(ModelInspector inspector) => + new ElementModelSerializationEngine(inspector, + buildXmlParsingSettings(Mode.BackwardsCompatible), + buildJsonParsingSettings(Mode.BackwardsCompatible), + buildPocoBuilderSettings(Mode.BackwardsCompatible)); /// - /// Create an implementation of which allows for manual specification of the json serializer and deserializer. - /// The Xml parser in this serialization engine is completely default. + /// Create an implementation of which uses the legacy parser and serializer + /// configured to allow errors and just continue parsing. Note that this may mean data loss. /// - /// A preconfigured json deserializer - /// A preconfigured json serializer - /// - internal static IFhirSerializationEngine WithCustomJsonSerializers(BaseFhirJsonPocoDeserializer jsonDeserializer, - BaseFhirJsonPocoSerializer jsonSerializer) - { - return new PocoSerializationEngine(jsonDeserializer, jsonSerializer); - } + public static IFhirSerializationEngine Ostrich(ModelInspector inspector) => + new ElementModelSerializationEngine(inspector, + buildXmlParsingSettings(Mode.Ostrich), + buildJsonParsingSettings(Mode.Ostrich), + buildPocoBuilderSettings(Mode.Ostrich)); } -} -#nullable restore + + /// + /// Create an implementation of configured to flag all parsing errors, + /// which uses the new Poco-based parser and serializer. + /// + public static IFhirSerializationEngine Strict(ModelInspector inspector, + FhirJsonConverterOptions? converterOptions = null) => + new PocoSerializationEngine(inspector, converterOptions: converterOptions); + + /// + /// Create an implementation of configured to ignore recoverable errors, + /// which uses the new Poco-based parser and serializer. + /// + public static IFhirSerializationEngine Recoverable(ModelInspector inspector, + FhirJsonConverterOptions? converterOptions = null) => + new PocoSerializationEngine(inspector, FilterPredicateExtensions.IsRecoverableIssue, converterOptions: converterOptions); + + /// + /// Create an implementation of which uses the new Poco-based parser and + /// serializers and is configured to allow errors that could occur when reading data from newer releases of FHIR. + /// Note that this parser may drop data that cannot be captured in the POCO model, such as new elements in future + /// FHIR releases. + /// + public static IFhirSerializationEngine BackwardsCompatible(ModelInspector inspector, + FhirJsonConverterOptions? converterOptions = null) => + new PocoSerializationEngine(inspector, FilterPredicateExtensions.IsBackwardsCompatibilityIssue, converterOptions); + + /// + /// Create an implementation of configured to allow errors + /// and just continue parsing. Note that this may mean data loss. + /// + public static IFhirSerializationEngine Ostrich(ModelInspector inspector, + FhirJsonConverterOptions? converterOptions = null) => + new PocoSerializationEngine( + inspector, + _ => true, + (converterOptions ?? new FhirJsonConverterOptions()) with {Validator = null}, + xmlSettings: new FhirXmlPocoDeserializerSettings {Validator = null}); + + /// + /// Create an implementation of which allows for manual configuration + /// of most behaviour. See parameters for more information. + /// + /// + /// A predicate specifying which errors to ignore when parsing + /// The settings to be used by the engine to deserialize JSON sources + /// The settings to be used by the engine to deserialize XML sources + /// + public static IFhirSerializationEngine Custom(ModelInspector inspector, Predicate ignoreFilter, + FhirJsonConverterOptions? converterOptions = null, + FhirXmlPocoDeserializerSettings? xmlSerializerSettings = null) + { + return new PocoSerializationEngine(inspector, ignoreFilter, converterOptions); + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine.cs b/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine.cs index 82c3fa789f..ea07bc335b 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine.cs @@ -29,28 +29,15 @@ internal partial class PocoSerializationEngine : IFhirSerializationEngine private readonly ModelInspector _inspector; internal Predicate IgnoreFilter { get; set; } - internal PocoSerializationEngine(ModelInspector inspector, Predicate? ignoreFilter = null, FhirJsonPocoDeserializerSettings? jsonDeserializerSettings = null, FhirJsonPocoSerializerSettings? jsonSerializerSettings = null, FhirXmlPocoDeserializerSettings? xmlSettings = null) + internal PocoSerializationEngine(ModelInspector inspector, Predicate? ignoreFilter = null, + FhirJsonConverterOptions? converterOptions = null, FhirXmlPocoDeserializerSettings? xmlSettings = null) { _inspector = inspector; IgnoreFilter = ignoreFilter ?? (_ => false); - _jsonDeserializerSettings = jsonDeserializerSettings ?? new FhirJsonPocoDeserializerSettings(); - _jsonSerializerSettings = jsonSerializerSettings ?? new FhirJsonPocoSerializerSettings(); + _jsonConverterOptions = converterOptions ?? new FhirJsonConverterOptions(); _xmlSettings = xmlSettings ?? new FhirXmlPocoDeserializerSettings(); } - internal PocoSerializationEngine(BaseFhirJsonPocoDeserializer deserializer, - BaseFhirJsonPocoSerializer serializer) - { - _jsonDeserializer = deserializer; - _jsonSerializer = serializer; - // dirty, but this constructor is really not supposed to be supported for much longer - var inspectorfield = - typeof(BaseFhirJsonPocoDeserializer).GetField("_inspector", BindingFlags.NonPublic | BindingFlags.Instance); - _inspector = (inspectorfield!.GetValue(_jsonDeserializer) as ModelInspector)!; - IgnoreFilter = _ => false; - _xmlSettings = new FhirXmlPocoDeserializerSettings(); - } - private Base deserializeAndFilterErrors(TryDeserializer deserializer) { var (instance, issues) = deserializer(); @@ -58,6 +45,4 @@ private Base deserializeAndFilterErrors(TryDeserializer deserializer) return relevantIssues.Any() ? throw new DeserializationFailedException(instance, relevantIssues) : instance!; } -} - -#nullable restore \ No newline at end of file +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Json.cs b/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Json.cs index 5224a23247..4027de8b34 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Json.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Json.cs @@ -9,17 +9,16 @@ namespace Hl7.Fhir.Serialization; internal partial class PocoSerializationEngine { - private readonly FhirJsonPocoDeserializerSettings? _jsonDeserializerSettings; - private readonly FhirJsonPocoSerializerSettings? _jsonSerializerSettings; - + private readonly FhirJsonConverterOptions? _jsonConverterOptions; + private BaseFhirJsonPocoDeserializer? _jsonDeserializer; - private BaseFhirJsonPocoSerializer? _jsonSerializer; + private BaseFhirJsonSerializer? _jsonSerializer; private BaseFhirJsonPocoDeserializer getJsonDeserializer() => - _jsonDeserializer ??= new BaseFhirJsonPocoDeserializer(_inspector, _jsonDeserializerSettings!); + _jsonDeserializer ??= new BaseFhirJsonPocoDeserializer(_inspector, _jsonConverterOptions!); - private BaseFhirJsonPocoSerializer getJsonSerializer() => - _jsonSerializer ??= new BaseFhirJsonPocoSerializer(_inspector.FhirRelease, _jsonSerializerSettings!); + private BaseFhirJsonSerializer getJsonSerializer() => + _jsonSerializer ??= new BaseFhirJsonSerializer(_inspector); /// public Resource DeserializeFromJson(string data) @@ -55,7 +54,8 @@ public Base DeserializeObjectFromJson(Type targetType, ref Utf8JsonReader reader ///
/// An instance of Base or any of its children /// The JSON writer - public void SerializeToJsonWriter(Base instance, Utf8JsonWriter writer) => getJsonSerializer().Serialize(instance, writer); + public void SerializeToJsonWriter(Base instance, Utf8JsonWriter writer) => + getJsonSerializer().Serialize(instance, writer, _jsonConverterOptions?.SummaryFilter); // overload necessary since ref structs cannot be captured in the lambda private Resource deserializeAndFilterErrors(BaseFhirJsonPocoDeserializer deserializer, ref Utf8JsonReader reader) @@ -74,6 +74,4 @@ private Base deserializeObjectAndFilterErrors(Type targetType, BaseFhirJsonPocoD return relevantIssues.Any() ? throw new DeserializationFailedException(instance, relevantIssues) : instance!; } -} - -#nullable restore \ No newline at end of file +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Xml.cs b/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Xml.cs index 385ff53e97..52d7741a4a 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Xml.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/PocoSerializationEngine_Xml.cs @@ -16,13 +16,13 @@ internal partial class PocoSerializationEngine private readonly FhirXmlPocoDeserializerSettings _xmlSettings; private BaseFhirXmlPocoDeserializer? _xmlDeserializer; - private BaseFhirXmlPocoSerializer? _xmlSerializer; + private BaseFhirXmlSerializer? _xmlSerializer; private BaseFhirXmlPocoDeserializer getXmlDeserializer() => _xmlDeserializer ??= new BaseFhirXmlPocoDeserializer(_inspector, _xmlSettings); - private BaseFhirXmlPocoSerializer getXmlSerializer() => - _xmlSerializer ??= new BaseFhirXmlPocoSerializer(_inspector.FhirRelease); + private BaseFhirXmlSerializer getXmlSerializer() => + _xmlSerializer ??= new BaseFhirXmlSerializer(_inspector); /// public Resource DeserializeFromXml(string data) diff --git a/src/Hl7.Fhir.Base/Specification/IStructureDefinitionSummaryProvider.cs b/src/Hl7.Fhir.Base/Specification/IStructureDefinitionSummaryProvider.cs index b06e1c2ff7..bbca321fcd 100644 --- a/src/Hl7.Fhir.Base/Specification/IStructureDefinitionSummaryProvider.cs +++ b/src/Hl7.Fhir.Base/Specification/IStructureDefinitionSummaryProvider.cs @@ -10,91 +10,108 @@ using Hl7.Fhir.Utility; using System.Collections.Generic; -namespace Hl7.Fhir.Specification +namespace Hl7.Fhir.Specification; + +public interface IElementDefinitionSummary // ElementDefinition { - public interface IElementDefinitionSummary // ElementDefinition - { - string ElementName { get; } - bool IsCollection { get; } - bool IsRequired { get; } - bool InSummary { get; } - bool IsChoiceElement { get; } - bool IsResource { get; } - - /// - /// If this modifies the meaning of other elements - /// - bool IsModifier { get; } - - ITypeSerializationInfo[] Type { get; } - - /// - /// Logical Models where a choice type is represented by ElementDefinition.representation = typeAttr might define a default type (elementdefinition-defaulttype extension). null in most cases. - /// - string DefaultTypeName { get; } - - /// - /// This is the namespace used for the xml node representing this element. - /// Only need to be set if different from "http://hl7.org/fhir". - /// - string NonDefaultNamespace { get; } - - /// - /// The kind of node used to represent this element in XML. - /// Default is . - /// - XmlRepresentation Representation { get; } - - int Order { get; } - } + string ElementName { get; } + /// + /// Whether this element may repeat. + /// + bool IsCollection { get; } + /// + /// Whether this element may repeat, with a minimum cardinality of 1. + /// + bool IsRequired { get; } - public interface ITypeSerializationInfo - { - } + /// + /// Whether this element is marked as "in summary" in the FHIR specification. + /// + bool InSummary { get; } /// - /// A class representing a complex type, with child elements. + /// Whether this element is a choice element, allowing multiple types. /// - /// - /// In FHIR, this interface represents definitions of Resources, datatypes and BackboneElements. - /// BackboneElements will have the TypeName set to "BackboneElement" (in resources) or "Element" (in datatypes) - /// and IsAbstract set to true. - /// - public interface IStructureDefinitionSummary : ITypeSerializationInfo - { - string TypeName { get; } - bool IsAbstract { get; } - bool IsResource { get; } + bool IsChoiceElement { get; } - IReadOnlyCollection GetElements(); - } + /// + /// Whether the element contains a resource (e.g. a contained resource). + /// + bool IsResource { get; } - public interface IStructureDefinitionReference : ITypeSerializationInfo - { - string ReferredType { get; } - } + /// + /// If this modifies the meaning of other elements + /// + bool IsModifier { get; } - public interface IStructureDefinitionSummaryProvider - { - IStructureDefinitionSummary Provide(string canonical); - } + ITypeSerializationInfo[] Type { get; } + + /// + /// Logical Models where a choice type is represented by ElementDefinition.representation = typeAttr might define a default type (elementdefinition-defaulttype extension). null in most cases. + /// + string DefaultTypeName { get; } - public static class TypeSerializationInfoExtensions + /// + /// This is the namespace used for the xml node representing this element. + /// Only need to be set if different from "http://hl7.org/fhir". + /// + string NonDefaultNamespace { get; } + + /// + /// The kind of node used to represent this element in XML. + /// Default is . + /// + XmlRepresentation Representation { get; } + + /// + /// Relative order of this element compared to other elements. + /// + int Order { get; } +} + + + +public interface ITypeSerializationInfo; + +/// +/// A class representing a complex type, with child elements. +/// +/// +/// In FHIR, this interface represents definitions of Resources, datatypes and BackboneElements. +/// BackboneElements will have the TypeName set to "BackboneElement" (in resources) or "Element" (in datatypes) +/// and IsAbstract set to true. +/// +public interface IStructureDefinitionSummary : ITypeSerializationInfo +{ + string TypeName { get; } + bool IsAbstract { get; } + bool IsResource { get; } + + IReadOnlyCollection GetElements(); +} + +public interface IStructureDefinitionReference : ITypeSerializationInfo +{ + string ReferredType { get; } +} + +public interface IStructureDefinitionSummaryProvider +{ + IStructureDefinitionSummary Provide(string canonical); +} + +public static class TypeSerializationInfoExtensions +{ + public static string GetTypeName(this ITypeSerializationInfo info) { - public static string GetTypeName(this ITypeSerializationInfo info) + return info switch { - switch (info) - { - case IStructureDefinitionReference tr: - return tr.ReferredType; - case IStructureDefinitionSummary ct: - return ct.TypeName; - default: - throw Error.NotSupported($"Don't know how to derive type information from type {info.GetType()}"); - } - } - + IStructureDefinitionReference tr => tr.ReferredType, + IStructureDefinitionSummary ct => ct.TypeName, + _ => throw Error.NotSupported($"Don't know how to derive type information from type {info.GetType()}") + }; } -} + +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs b/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs index 1c54f33bff..8b31d9c50c 100644 --- a/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs +++ b/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs @@ -8,6 +8,7 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +using Hl7.Fhir.Model; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -15,721 +16,746 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; -namespace Hl7.Fhir.Utility -{ - public static class SerializationUtil - { - private const string XML_XSD_RESOURCENAME = "xml.xsd"; - private const string XMLDSIGCORESCHEMA_XSD_RESOURCENAME = "xmldsig-core-schema.xsd"; - private const string FHIRXHTML_XSD_RESOURCENAME = "fhir-xhtml.xsd"; +namespace Hl7.Fhir.Utility; - /// - /// A set of Xsd schemas used by the full FHIR schemaset. These include xml.xsd, xmldsig-core-schema.xsd and fhir-xhtml.xsd. - /// - public static readonly IncludedXsdSchemaSet BASEFHIRSCHEMAS = - new(typeof(SerializationUtil).Assembly, XML_XSD_RESOURCENAME, XMLDSIGCORESCHEMA_XSD_RESOURCENAME, FHIRXHTML_XSD_RESOURCENAME); - - private readonly static Regex xml = new Regex("""^\s*<[^>]+>""", RegexOptions.Compiled); - private readonly static Regex json = new Regex("""^\s*(\{\s*("[^"]+"\s*:.*)?\})\s*$""", RegexOptions.Compiled | RegexOptions.Singleline); +public static class SerializationUtil +{ + private const string XML_XSD_RESOURCENAME = "xml.xsd"; + private const string XMLDSIGCORESCHEMA_XSD_RESOURCENAME = "xmldsig-core-schema.xsd"; + private const string FHIRXHTML_XSD_RESOURCENAME = "fhir-xhtml.xsd"; + /// + /// A set of Xsd schemas used by the full FHIR schemaset. These include xml.xsd, xmldsig-core-schema.xsd and fhir-xhtml.xsd. + /// + public static readonly IncludedXsdSchemaSet BASEFHIRSCHEMAS = + new(typeof(SerializationUtil).Assembly, XML_XSD_RESOURCENAME, XMLDSIGCORESCHEMA_XSD_RESOURCENAME, FHIRXHTML_XSD_RESOURCENAME); - public static bool ProbeIsXml(string data) => xml.IsMatch(data); + private readonly static Regex XML = new("""^\s*<[^>]+>""", RegexOptions.Compiled); + private readonly static Regex JSON = new("""^\s*(\{\s*("[^"]+"\s*:.*)?\})\s*$""", RegexOptions.Compiled | RegexOptions.Singleline); - public static bool ProbeIsFhirXml(string data) => - ProbeIsXml(data) && (data.Contains($"'{XmlNs.FHIR}'") || data.Contains($"\"{XmlNs.FHIR}\"")); + public static bool ProbeIsXml(string data) => XML.IsMatch(data); + public static bool ProbeIsFhirXml(string data) => + ProbeIsXml(data) && (data.Contains($"'{XmlNs.FHIR}'") || data.Contains($"\"{XmlNs.FHIR}\"")); - public static bool ProbeIsJson(string data) => json.IsMatch(data); + public static bool ProbeIsJson(string data) => JSON.IsMatch(data); - public static bool ProbeIsFhirJson(string data) => - ProbeIsJson(data) && data.Contains($"\"resourceType\""); + public static bool ProbeIsFhirJson(string data) => + ProbeIsJson(data) && data.Contains($"\"resourceType\""); - private static XDocument XDocumentFromReaderInternal(XmlReader reader) + private static XDocument xDocumentFromReaderInternal(XmlReader reader) + { + try { - try - { - return XDocument.Load(WrapXmlReader(reader, ignoreComments: false), - LoadOptions.SetLineInfo); - } - catch (XmlException xec) - { - throw Error.Format("Invalid Xml encountered. Details: " + xec.Message, xec); - } + return XDocument.Load(WrapXmlReader(reader, ignoreComments: false), + LoadOptions.SetLineInfo); } + catch (XmlException xec) + { + throw Error.Format("Invalid Xml encountered. Details: " + xec.Message, xec); + } + } - public static XDocument XDocumentFromReader(XmlReader reader, bool ignoreComments = true) - => XDocumentFromReaderInternal(WrapXmlReader(reader, ignoreComments)); - - public static Task XDocumentFromReaderAsync(XmlReader reader, bool ignoreComments = true) - => Task.FromResult(XDocumentFromReaderInternal(WrapXmlReader(reader, ignoreComments, async: true))); + public static XDocument XDocumentFromReader(XmlReader reader, bool ignoreComments = true) + => xDocumentFromReaderInternal(WrapXmlReader(reader, ignoreComments)); - /// - public static JObject JObjectFromReader(JsonReader reader) - { - // Override settings, sorry but this is vital - reader.DateParseHandling = DateParseHandling.None; - reader.FloatParseHandling = FloatParseHandling.Decimal; + public static Task XDocumentFromReaderAsync(XmlReader reader, bool ignoreComments = true) + => Task.FromResult(xDocumentFromReaderInternal(WrapXmlReader(reader, ignoreComments, async: true))); - try - { - return JObject.Load(reader, - new JsonLoadSettings - { - CommentHandling = CommentHandling.Ignore, - LineInfoHandling = LineInfoHandling.Load - }); - } - catch (JsonReaderException jre) - { - throw new FormatException($"Invalid Json encountered. Details: " + jre.Message, jre); - } - } + /// + public static JObject JObjectFromReader(JsonReader reader) + { + // Override settings, sorry but this is vital + reader.DateParseHandling = DateParseHandling.None; + reader.FloatParseHandling = FloatParseHandling.Decimal; - public static async Task JObjectFromReaderAsync(JsonReader reader) + try { - // Override settings, sorry but this is vital - reader.DateParseHandling = DateParseHandling.None; - reader.FloatParseHandling = FloatParseHandling.Decimal; - - try - { - return await JObject.LoadAsync(reader, - new JsonLoadSettings - { - CommentHandling = CommentHandling.Ignore, - LineInfoHandling = LineInfoHandling.Load - }).ConfigureAwait(false); - } - catch (JsonReaderException jre) - { - throw new FormatException($"Invalid Json encountered. Details: " + jre.Message, jre); - } + return JObject.Load(reader, + new JsonLoadSettings + { + CommentHandling = CommentHandling.Ignore, + LineInfoHandling = LineInfoHandling.Load + }); } - - public static XDocument XDocumentFromXmlText(string xml, bool ignoreComments = true) + catch (JsonReaderException jre) { - using (var reader = XmlReaderFromXmlText(xml)) - { - return XDocumentFromReaderInternal(reader); - } + throw new FormatException($"Invalid Json encountered. Details: " + jre.Message, jre); } + } - /// - public static JObject JObjectFromJsonText(string json) + public static async Task JObjectFromReaderAsync(JsonReader reader) + { + // Override settings, sorry but this is vital + reader.DateParseHandling = DateParseHandling.None; + reader.FloatParseHandling = FloatParseHandling.Decimal; + + try { - using (var reader = JsonReaderFromJsonText(json)) - { - return JObjectFromReader(reader); - } + return await JObject.LoadAsync(reader, + new JsonLoadSettings + { + CommentHandling = CommentHandling.Ignore, + LineInfoHandling = LineInfoHandling.Load + }).ConfigureAwait(false); } - - public static async Task JObjectFromJsonTextAsync(string json) + catch (JsonReaderException jre) { - using (var reader = JsonReaderFromJsonText(json)) - { - return await JObjectFromReaderAsync(reader).ConfigureAwait(false); - } + throw new FormatException($"Invalid Json encountered. Details: " + jre.Message, jre); } + } - public static XmlReader XmlReaderFromXmlText(string xml, bool ignoreComments = true) - => WrapXmlReader(XmlReader.Create(new StringReader(SerializationUtil.SanitizeXml(xml))), ignoreComments); - - public static Task XmlReaderFromXmlTextAsync(string xml, bool ignoreComments = true) - => Task.FromResult(WrapXmlReader(XmlReader.Create(new StringReader(SerializationUtil.SanitizeXml(xml))), ignoreComments, async: true)); - - public static JsonReader JsonReaderFromJsonText(string json) - => JsonReaderFromTextReader(new StringReader(json)); - - public static XmlReader XmlReaderFromStream(Stream input, bool ignoreComments = true) - => WrapXmlReader(XmlReader.Create(input), ignoreComments); - - public static JsonReader JsonReaderFromStream(Stream input) - => JsonReaderFromTextReader(new StreamReader(input)); - - private static JsonReader JsonReaderFromTextReader(TextReader input) + public static XDocument XDocumentFromXmlText(string xml, bool ignoreComments = true) + { + using (var reader = XmlReaderFromXmlText(xml)) { - return new JsonTextReader(input) - { - DateParseHandling = DateParseHandling.None, - FloatParseHandling = FloatParseHandling.Decimal, - CloseInput = false // Unbelievable, the default is 'true' (and is false for the XmlReaderSettings :-() - }; + return xDocumentFromReaderInternal(reader); } + } + public static XmlReader XmlReaderFromXmlText(string xml, bool ignoreComments = true) + => WrapXmlReader(XmlReader.Create(new StringReader(SerializationUtil.SanitizeXml(xml))), ignoreComments); - public static XmlReader WrapXmlReader(XmlReader xmlReader, bool ignoreComments = true, bool async = false) - { - var settings = new XmlReaderSettings - { - IgnoreComments = ignoreComments, - IgnoreProcessingInstructions = true, - IgnoreWhitespace = true, - DtdProcessing = DtdProcessing.Prohibit, - Async = async, - }; + public static Task XmlReaderFromXmlTextAsync(string xml, bool ignoreComments = true) + => Task.FromResult(WrapXmlReader(XmlReader.Create(new StringReader(SerializationUtil.SanitizeXml(xml))), ignoreComments, async: true)); - return XmlReader.Create(xmlReader, settings); - } + public static JsonReader JsonReaderFromJsonText(string json) + => jsonReaderFromTextReader(new StringReader(json)); - /// - public static byte[] WriteXmlToBytes(Action serializer) - { - // [WMR 20160421] Explicit disposal - using (MemoryStream stream = new MemoryStream()) - { - XmlWriterSettings settings = new XmlWriterSettings - { - Encoding = new UTF8Encoding(false), - OmitXmlDeclaration = true, - NewLineHandling = NewLineHandling.Entitize - }; + public static XmlReader XmlReaderFromStream(Stream input, bool ignoreComments = true) + => WrapXmlReader(XmlReader.Create(input), ignoreComments); - using (XmlWriter xw = XmlWriter.Create(stream, settings)) - { - serializer(xw); - xw.Flush(); - return stream.ToArray(); - } - } - } + public static JsonReader JsonReaderFromStream(Stream input) + => jsonReaderFromTextReader(new StreamReader(input)); - public static async Task WriteXmlToBytesAsync(Func serializer) + private static JsonReader jsonReaderFromTextReader(TextReader input) + { + return new JsonTextReader(input) { - // [WMR 20160421] Explicit disposal - using (MemoryStream stream = new MemoryStream()) - { - XmlWriterSettings settings = new XmlWriterSettings - { - Encoding = new UTF8Encoding(false), - OmitXmlDeclaration = true, - NewLineHandling = NewLineHandling.Entitize, - Async = true, - }; - - using (XmlWriter xw = XmlWriter.Create(stream, settings)) - { - await serializer(xw).ConfigureAwait(false); - await xw.FlushAsync().ConfigureAwait(false); - return stream.ToArray(); - } - } - } + DateParseHandling = DateParseHandling.None, + FloatParseHandling = FloatParseHandling.Decimal, + CloseInput = false // Unbelievable, the default is 'true' (and is false for the XmlReaderSettings :-() + }; + } - public static string WriteXmlToString(T value, Action serializer, bool pretty = false, bool appendNewLine = false) => - WriteXmlToString(w => serializer(value, w), pretty, appendNewLine); - /// - public static string WriteXmlToString(Action serializer, bool pretty = false, bool appendNewLine = false) + public static XmlReader WrapXmlReader(XmlReader xmlReader, bool ignoreComments = true, bool async = false) + { + var settings = new XmlReaderSettings { - StringBuilder sb = new StringBuilder(); + IgnoreComments = ignoreComments, + IgnoreProcessingInstructions = true, + IgnoreWhitespace = true, + DtdProcessing = DtdProcessing.Prohibit, + Async = async, + }; + + return XmlReader.Create(xmlReader, settings); + } + public static byte[] WriteXmlToBytes(Action serializer, bool pretty = false) + { + // [WMR 20160421] Explicit disposal + using (MemoryStream stream = new MemoryStream()) + { XmlWriterSettings settings = new XmlWriterSettings { + Encoding = new UTF8Encoding(false), OmitXmlDeclaration = true, NewLineHandling = NewLineHandling.Entitize, Indent = pretty }; - // [WMR 20160421] Explicit disposal - using (XmlWriter xw = XmlWriter.Create(sb, settings)) + using (XmlWriter xw = XmlWriter.Create(stream, settings)) { serializer(xw); xw.Flush(); - return appendNewLine ? sb.AppendLine().ToString() : sb.ToString(); + return stream.ToArray(); } } + } - public static async Task WriteXmlToStringAsync(Func serializer, bool pretty = false, bool appendNewLine = false) + public static async Task WriteXmlToBytesAsync(Func serializer, bool pretty = false) + { + // [WMR 20160421] Explicit disposal + using (MemoryStream stream = new MemoryStream()) { - StringBuilder sb = new StringBuilder(); - XmlWriterSettings settings = new XmlWriterSettings { + Encoding = new UTF8Encoding(false), OmitXmlDeclaration = true, NewLineHandling = NewLineHandling.Entitize, - Indent = pretty, Async = true, + Indent = pretty }; - // [WMR 20160421] Explicit disposal - using (XmlWriter xw = XmlWriter.Create(sb, settings)) + using (XmlWriter xw = XmlWriter.Create(stream, settings)) { await serializer(xw).ConfigureAwait(false); await xw.FlushAsync().ConfigureAwait(false); - return appendNewLine ? sb.AppendLine().ToString() : sb.ToString(); + return stream.ToArray(); } } + } - /// - public static XDocument WriteXmlToDocument(Action serializer) - { - var doc = new XDocument(); + public static string WriteXmlToString(Action serializer, bool pretty = false) + { + StringBuilder sb = new(); - using (XmlWriter xw = doc.CreateWriter()) - { - serializer(xw); - xw.Flush(); - } + XmlWriterSettings settings = new() + { + OmitXmlDeclaration = true, + NewLineHandling = NewLineHandling.Entitize, + Indent = pretty + }; + + // [WMR 20160421] Explicit disposal + using XmlWriter xw = XmlWriter.Create(sb, settings); + serializer(xw); + xw.Flush(); + return sb.ToString(); + } - return doc; - } + public static async Task WriteXmlToStringAsync(Func serializer, bool pretty = false) + { + var sb = new StringBuilder(); - public static async Task WriteXmlToDocumentAsync(Func serializer) + var settings = new XmlWriterSettings { - var doc = new XDocument(); + OmitXmlDeclaration = true, + NewLineHandling = NewLineHandling.Entitize, + Indent = pretty, + Async = true, + }; + + // [WMR 20160421] Explicit disposal + using XmlWriter xw = XmlWriter.Create(sb, settings); + await serializer(xw).ConfigureAwait(false); + await xw.FlushAsync().ConfigureAwait(false); + return sb.ToString(); + } - using (XmlWriter xw = doc.CreateWriter()) - { - await serializer(xw).ConfigureAwait(false); - await xw.FlushAsync().ConfigureAwait(false); - } + /// + public static XDocument WriteXmlToDocument(Action serializer) + { + var doc = new XDocument(); - return doc; - } + using XmlWriter xw = doc.CreateWriter(); + serializer(xw); + xw.Flush(); - /// - public static string WriteJsonToString(Action serializer, bool pretty = false, bool appendNewLine = false) - { - StringBuilder resultBuilder = new StringBuilder(); + return doc; + } - // [WMR 20160421] Explicit disposal - using (StringWriter sw = new StringWriter(resultBuilder)) - using (JsonWriter jw = SerializationUtil.CreateJsonTextWriter(sw)) - { - jw.Formatting = pretty ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None; - serializer(jw); - jw.Flush(); - return appendNewLine ? resultBuilder.AppendLine().ToString() : resultBuilder.ToString(); - } - } + public static async Task WriteXmlToDocumentAsync(Func serializer) + { + var doc = new XDocument(); - public static async Task WriteJsonToStringAsync(Func serializer, bool pretty = false, bool appendNewLine = false) - { - StringBuilder resultBuilder = new StringBuilder(); + using XmlWriter xw = doc.CreateWriter(); + await serializer(xw).ConfigureAwait(false); + await xw.FlushAsync().ConfigureAwait(false); - // [WMR 20160421] Explicit disposal - using (StringWriter sw = new StringWriter(resultBuilder)) - using (JsonWriter jw = SerializationUtil.CreateJsonTextWriter(sw)) - { - jw.Formatting = pretty ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None; - await serializer(jw).ConfigureAwait(false); - await jw.FlushAsync().ConfigureAwait(false); - return appendNewLine ? resultBuilder.AppendLine().ToString() : resultBuilder.ToString(); - } - } + return doc; + } - /// - public static byte[] WriteJsonToBytes(Action serializer) - { - // [WMR 20160421] Explicit disposal - using (MemoryStream stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, new UTF8Encoding(false))) - using (JsonWriter jw = SerializationUtil.CreateJsonTextWriter(sw)) - { - serializer(jw); - jw.Flush(); - sw.Flush(); - return stream.ToArray(); - } - } - } + public static string WriteJsonToString(Action serializer, bool pretty = false) + { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = pretty}); + serializer(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } - public static async Task WriteJsonToBytesAsync(Func serializer) - { - // [WMR 20160421] Explicit disposal - using (MemoryStream stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, new UTF8Encoding(false))) - using (JsonWriter jw = SerializationUtil.CreateJsonTextWriter(sw)) - { - await serializer(jw); - await jw.FlushAsync().ConfigureAwait(false); - await sw.FlushAsync().ConfigureAwait(false); - return stream.ToArray(); - } - } - } + public static string WriteJsonToString(Action serializer, bool pretty = false) + { + var resultBuilder = new StringBuilder(); - /// - public static JObject? WriteJsonToDocument(Action serializer) - { - // [WMR 20180409] Triggers runtime exception "Can not add Newtonsoft.Json.Linq.JObject to Newtonsoft.Json.Linq.JObject." - // JsonDomFhirWriter.WriteEndProperty() => _root.WriteTo(jw) => jw.WriteStartObject() => exception... - //var doc = new JObject(); + using var sw = new StringWriter(resultBuilder); + using JsonWriter jw = CreateJsonTextWriter(sw); - // JConstructor / JArray works, extract and return first child node - var doc = new JArray(); + jw.Formatting = pretty ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None; + serializer(jw); + jw.Flush(); + return resultBuilder.ToString(); + } - using (JsonWriter jw = doc.CreateWriter()) - { - serializer(jw); - jw.Flush(); - } + public static async Task WriteJsonToStringAsync(Func serializer, bool pretty = false) + { + var resultBuilder = new StringBuilder(); - System.Diagnostics.Debug.Assert(doc.Count == 1); - return doc.First as JObject; - } + await using var sw = new StringWriter(resultBuilder); + using var jw = CreateJsonTextWriter(sw); + + jw.Formatting = pretty ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None; + await serializer(jw).ConfigureAwait(false); + await jw.FlushAsync().ConfigureAwait(false); + return resultBuilder.ToString(); + } + + public static byte[] WriteJsonToBytes(Action serializer, bool pretty = false) + { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = pretty }); + serializer(writer); + writer.Flush(); + return stream.ToArray(); + } + + public static byte[] WriteJsonToBytes(Action serializer, bool pretty = false) + { + using var stream = new MemoryStream(); + using var sw = new StreamWriter(stream, new UTF8Encoding(false)); + using var jw = CreateJsonTextWriter(sw); + + jw.Formatting = pretty ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None; + serializer(jw); + jw.Flush(); + sw.Flush(); + return stream.ToArray(); + } + + public static async Task WriteJsonToBytesAsync(Func serializer, bool pretty = false) + { + using var stream = new MemoryStream(); + await using var sw = new StreamWriter(stream, new UTF8Encoding(false)); + using var jw = CreateJsonTextWriter(sw); + + jw.Formatting = pretty ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None; + await serializer(jw).ConfigureAwait(false); + await jw.FlushAsync().ConfigureAwait(false); + await sw.FlushAsync().ConfigureAwait(false); + return stream.ToArray(); + } + + /// + public static JObject? WriteJsonToDocument(Action serializer) + { + // [WMR 20180409] Triggers runtime exception "Can not add Newtonsoft.Json.Linq.JObject to Newtonsoft.Json.Linq.JObject." + // JsonDomFhirWriter.WriteEndProperty() => _root.WriteTo(jw) => jw.WriteStartObject() => exception... + //var doc = new JObject(); + + // JConstructor / JArray works, extract and return first child node + var doc = new JArray(); - public static async Task WriteJsonToDocumentAsync(Func serializer) + using (JsonWriter jw = doc.CreateWriter()) { - // [WMR 20180409] Triggers runtime exception "Can not add Newtonsoft.Json.Linq.JObject to Newtonsoft.Json.Linq.JObject." - // JsonDomFhirWriter.WriteEndProperty() => _root.WriteTo(jw) => jw.WriteStartObject() => exception... - //var doc = new JObject(); + serializer(jw); + jw.Flush(); + } - // JConstructor / JArray works, extract and return first child node - var doc = new JArray(); + System.Diagnostics.Debug.Assert(doc.Count == 1); + return doc.First as JObject; + } - using (JsonWriter jw = doc.CreateWriter()) - { - await serializer(jw).ConfigureAwait(false); - await jw.FlushAsync().ConfigureAwait(false); - } + public static async Task WriteJsonToDocumentAsync(Func serializer) + { + // [WMR 20180409] Triggers runtime exception "Can not add Newtonsoft.Json.Linq.JObject to Newtonsoft.Json.Linq.JObject." + // JsonDomFhirWriter.WriteEndProperty() => _root.WriteTo(jw) => jw.WriteStartObject() => exception... + //var doc = new JObject(); + + // JConstructor / JArray works, extract and return first child node + var doc = new JArray(); - System.Diagnostics.Debug.Assert(doc.Count == 1); - return doc.First as JObject; + using (JsonWriter jw = doc.CreateWriter()) + { + await serializer(jw).ConfigureAwait(false); + await jw.FlushAsync().ConfigureAwait(false); } - public static JsonWriter CreateJsonTextWriter(TextWriter writer) => new BetterDecimalJsonTextWriter(writer); + System.Diagnostics.Debug.Assert(doc.Count == 1); + return doc.First as JObject; + } + + public static JsonWriter CreateJsonTextWriter(TextWriter writer) => new BetterDecimalJsonTextWriter(writer); - internal class BetterDecimalJsonTextWriter : JsonTextWriter + internal class BetterDecimalJsonTextWriter : JsonTextWriter + { + public BetterDecimalJsonTextWriter(TextWriter textWriter) : base(textWriter) { - public BetterDecimalJsonTextWriter(TextWriter textWriter) : base(textWriter) - { - } + } - public override void WriteValue(decimal value) - { - WriteRawValue(value.ToString(this.Culture)); - } + public override void WriteValue(decimal value) + { + WriteRawValue(value.ToString(this.Culture)); + } - public override void WriteValue(decimal? value) - { - if (value.HasValue) - WriteRawValue(value.Value.ToString(this.Culture)); - else - WriteNull(); - } + public override void WriteValue(decimal? value) + { + if (value.HasValue) + WriteRawValue(value.Value.ToString(this.Culture)); + else + WriteNull(); } + } - /// - /// Replace all the XML specific special characters with the XHTML equivalents - /// - /// - /// this is based on own research plus combining with results shown here: - /// http://www.codeproject.com/Articles/298519/Fast-Token-Replacement-in-Csharp - /// The RegEx approach does not require multiple passes or string creations - /// while replacing all the items. - /// It occurs in O(n) StringBuilder concatenations + O(n) dictionary lookups - /// - /// - /// - public static string SanitizeXml(string xml) - { - if (string.IsNullOrEmpty(xml)) - return xml; + /// + /// Replace all the XML specific special characters with the XHTML equivalents + /// + /// + /// this is based on own research plus combining with results shown here: + /// http://www.codeproject.com/Articles/298519/Fast-Token-Replacement-in-Csharp + /// The RegEx approach does not require multiple passes or string creations + /// while replacing all the items. + /// It occurs in O(n) StringBuilder concatenations + O(n) dictionary lookups + /// + /// + /// + public static string SanitizeXml(string xml) + { + if (string.IsNullOrEmpty(xml)) + return xml; - Dictionary xr = getXmlReplacements(); + Dictionary xr = getXmlReplacements(); - //s.Reset(); - //s.Start(); - string resultRE; - var matches = _re.Matches(xml); - if (matches.Count > 0) + string resultRe; + var matches = RE.Matches(xml); + if (matches.Count > 0) + { + StringBuilder sbRE = new StringBuilder(); + int currentPosition = 0; + foreach (Match m in matches) { - StringBuilder sbRE = new StringBuilder(); - int currentPosition = 0; - foreach (Match m in matches) + if (xr.TryGetValue(m.Value, out string? value)) { - if (xr.ContainsKey(m.Value)) - { - sbRE.Append(xml.Substring(currentPosition, m.Index - currentPosition)); - sbRE.Append(xr[m.Value]); - currentPosition = m.Index + m.Length; - } - // System.Diagnostics.Trace.WriteLine(String.Format("{0} - {1}: {2}", m.Index, m.Length, m.Value)); + sbRE.Append(xml.Substring(currentPosition, m.Index - currentPosition)); + sbRE.Append(value); + currentPosition = m.Index + m.Length; } - sbRE.Append(xml.Substring(currentPosition)); - resultRE = sbRE.ToString(); - } - else - { - resultRE = xml; + // System.Diagnostics.Trace.WriteLine(String.Format("{0} - {1}: {2}", m.Index, m.Length, m.Value)); } - - return resultRE; + sbRE.Append(xml.Substring(currentPosition)); + resultRe = sbRE.ToString(); } - - public static string[] RunFhirXhtmlSchemaValidation(string xmlText) + else { - try - { - var doc = SerializationUtil.XDocumentFromXmlText(xmlText); - return RunFhirXhtmlSchemaValidation(doc); - } - catch (FormatException fe) - { - return new[] { fe.Message }; - } + resultRe = xml; } - public static string[] RunFhirXhtmlSchemaValidation(XDocument doc) + return resultRe; + } + + public static string[] RunFhirXhtmlSchemaValidation(string xmlText) + { + try { - var result = new List(); + var doc = SerializationUtil.XDocumentFromXmlText(xmlText); + return RunFhirXhtmlSchemaValidation(doc); + } + catch (FormatException fe) + { + return new[] { fe.Message }; + } + } - if (!doc.Root!.AtXhtmlDiv()) - return new[] { $"Root element of XHTML is not a
from the XHTML namespace ({XmlNs.XHTML})." }; + public static string[] RunFhirXhtmlSchemaValidation(XDocument doc) + { + var result = new List(); - if (!hasContent(doc.Root!)) - return new[] { $"The narrative SHALL have some non-whitespace content." }; + if (!doc.Root!.AtXhtmlDiv()) + return new[] { $"Root element of XHTML is not a
from the XHTML namespace ({XmlNs.XHTML})." }; - doc.Validate(BASEFHIRSCHEMAS.CompiledSchemas, (s, a) => result.Add(a.Message)); - return result.ToArray(); + if (!hasContent(doc.Root!)) + return new[] { $"The narrative SHALL have some non-whitespace content." }; - // content consist of xml elements with non-whitespace content (text or an image) - static bool hasContent(XElement el) - => el.DescendantsAndSelf().Any(e => !string.IsNullOrWhiteSpace(e.Value) || e.Name.LocalName == "img"); - } + doc.Validate(BASEFHIRSCHEMAS.CompiledSchemas, (s, a) => result.Add(a.Message)); + return result.ToArray(); + // content consist of xml elements with non-whitespace content (text or an image) + static bool hasContent(XElement el) + => el.DescendantsAndSelf().Any(e => !string.IsNullOrWhiteSpace(e.Value) || e.Name.LocalName == "img"); + } - private static readonly Regex _re = new Regex("(&[a-zA-Z0-9]+;)", RegexOptions.Compiled | RegexOptions.CultureInvariant); - private static Dictionary? _xmlReplacements; - private static Dictionary getXmlReplacements() + + private static readonly Regex RE = new("(&[a-zA-Z0-9]+;)", RegexOptions.Compiled | RegexOptions.CultureInvariant); + private static Dictionary? _xmlReplacements; + private static Dictionary getXmlReplacements() + { + if (_xmlReplacements != null) + return _xmlReplacements; + + Dictionary xr = new() { - if (_xmlReplacements != null) - return _xmlReplacements; + { """, """ }, + { "&", "&" }, + { "<", "<" }, + { ">", ">" }, + { "'", "'" }, + { "Œ", "Œ" }, + { "œ", "œ" }, + { "Š", "Š" }, + { "š", "š" }, + { "Ÿ", "Ÿ" }, + { "ˆ", "ˆ" }, + { "˜", "˜" }, + { " ", " " }, + { " ", " " }, + { " ", " " }, + { "‌", "‌" }, + { "‍", "‍" }, + { "‎", "‎" }, + { "‏", "‏" }, + { "–", "–" }, + { "—", "—" }, + { "‘", "‘" }, + { "’", "’" }, + { "‚", "‚" }, + { "“", "“" }, + { "”", "”" }, + { "„", "„" }, + { "†", "†" }, + { "‡", "‡" }, + { "‰", "‰" }, + { "‹", "‹" }, + { "›", "›" }, + { "€", "€" }, + { "ƒ", "ƒ" }, + { "Α", "Α" }, + { "Β", "Β" }, + { "Γ", "Γ" }, + { "Δ", "Δ" }, + { "Ε", "Ε" }, + { "Ζ", "Ζ" }, + { "Η", "Η" }, + { "Θ", "Θ" }, + { "Ι", "Ι" }, + { "Κ", "Κ" }, + { "Λ", "Λ" }, + { "Μ", "Μ" }, + { "Ν", "Ν" }, + { "Ξ", "Ξ" }, + { "Ο", "Ο" }, + { "Π", "Π" }, + { "Ρ", "Ρ" }, + { "Σ", "Σ" }, + { "Τ", "Τ" }, + { "Υ", "Υ" }, + { "Φ", "Φ" }, + { "Χ", "Χ" }, + { "Ψ", "Ψ" }, + { "Ω", "Ω" }, + { "α", "α" }, + { "β", "β" }, + { "γ", "γ" }, + { "δ", "δ" }, + { "ε", "ε" }, + { "ζ", "ζ" }, + { "η", "η" }, + { "θ", "θ" }, + { "ι", "ι" }, + { "κ", "κ" }, + { "λ", "λ" }, + { "μ", "μ" }, + { "ν", "ν" }, + { "ξ", "ξ" }, + { "ο", "ο" }, + { "π", "π" }, + { "ρ", "ρ" }, + { "ς", "ς" }, + { "σ", "σ" }, + { "τ", "τ" }, + { "υ", "υ" }, + { "φ", "φ" }, + { "χ", "χ" }, + { "ψ", "ψ" }, + { "ω", "ω" }, + { "ϑ", "ϑ" }, + { "ϒ", "ϒ" }, + { "ϖ", "ϖ" }, + { "•", "•" }, + { "…", "…" }, + { "′", "′" }, + { "″", "″" }, + { "‾", "‾" }, + { "⁄", "⁄" }, + { "℘", "℘" }, + { "ℑ", "ℑ" }, + { "ℜ", "ℜ" }, + { "™", "™" }, + { "ℵ", "ℵ" }, + { "←", "←" }, + { "↑", "↑" }, + { "→", "→" }, + { "↓", "↓" }, + { "↔", "↔" }, + { "↵", "↵" }, + { "⇐", "⇐" }, + { "⇑", "⇑" }, + { "⇒", "⇒" }, + { "⇓", "⇓" }, + { "⇔", "⇔" }, + { "∀", "∀" }, + { "∂", "∂" }, + { "∃", "∃" }, + { "∅", "∅" }, + { "∇", "∇" }, + { "∈", "∈" }, + { "∉", "∉" }, + { "∋", "∋" }, + { "∏", "∏" }, + { "∑", "∑" }, + { "−", "−" }, + { "∗", "∗" }, + { "√", "√" }, + { "∝", "∝" }, + { "∞", "∞" }, + { "∠", "∠" }, + { "∧", "∧" }, + { "∨", "∨" }, + { "∩", "∩" }, + { "∪", "∪" }, + { "∫", "∫" }, + { "∴", "∴" }, + { "∼", "∼" }, + { "≅", "≅" }, + { "≈", "≈" }, + { "≠", "≠" }, + { "≡", "≡" }, + { "≤", "≤" }, + { "≥", "≥" }, + { "⊂", "⊂" }, + { "⊃", "⊃" }, + { "⊄", "⊄" }, + { "⊆", "⊆" }, + { "⊇", "⊇" }, + { "⊕", "⊕" }, + { "⊗", "⊗" }, + { "⊥", "⊥" }, + { "⋅", "⋅" }, + { "⌈", "⌈" }, + { "⌉", "⌉" }, + { "⌊", "⌊" }, + { "⌋", "⌋" }, + { "⟨", "〈" }, + { "⟩", "〉" }, + { "◊", "◊" }, + { "♠", "♠" }, + { "♣", "♣" }, + { "♥", "♥" }, + { "♦", "♦" }, + { " ", " " }, + { "¡", "¡" }, + { "¢", "¢" }, + { "£", "£" }, + { "¤", "¤" }, + { "¥", "¥" }, + { "¦", "¦" }, + { "§", "§" }, + { "¨", "¨" }, + { "©", "©" }, + { "ª", "ª" }, + { "«", "«" }, + { "¬", "¬" }, + { "­", "­" }, + { "®", "®" }, + { "¯", "¯" }, + { "°", "°" }, + { "±", "±" }, + { "²", "²" }, + { "³", "³" }, + { "´", "´" }, + { "µ", "µ" }, + { "¶", "¶" }, + { "·", "·" }, + { "¸", "¸" }, + { "¹", "¹" }, + { "º", "º" }, + { "»", "»" }, + { "¼", "¼" }, + { "½", "½" }, + { "¾", "¾" }, + { "¿", "¿" }, + { "À", "À" }, + { "Á", "Á" }, + { "Â", "Â" }, + { "Ã", "Ã" }, + { "Ä", "Ä" }, + { "Å", "Å" }, + { "Æ", "Æ" }, + { "Ç", "Ç" }, + { "È", "È" }, + { "É", "É" }, + { "Ê", "Ê" }, + { "Ë", "Ë" }, + { "Ì", "Ì" }, + { "Í", "Í" }, + { "Î", "Î" }, + { "Ï", "Ï" }, + { "Ð", "Ð" }, + { "Ñ", "Ñ" }, + { "Ò", "Ò" }, + { "Ó", "Ó" }, + { "Ô", "Ô" }, + { "Õ", "Õ" }, + { "Ö", "Ö" }, + { "×", "×" }, + { "Ø", "Ø" } + }; + + _xmlReplacements = xr; + return xr; + } + + /// + /// Add the SUBSETTED tag to the Meta of the resource and all its resource children. You need to use these + /// tags when you are serializing a summary of a resource. + /// + /// Will not add the tag to the root of a Bundle, since that is normally the container of + /// the subsetted resources. If the resource already contained a SUBSETTED tag, they will not + /// be reapplied. + public static void MarkSubsetted(Base instance) + { + addSubsetted(instance, true); + return; - Dictionary xr = new Dictionary + static void addSubsetted(Base instance, bool atRoot) + { + var isBundleAtRoot = instance is Bundle && atRoot; + + if (instance is Resource resource && !isBundleAtRoot) { - { """, """ }, - { "&", "&" }, - { "<", "<" }, - { ">", ">" }, - { "'", "'" }, - { "Œ", "Œ" }, - { "œ", "œ" }, - { "Š", "Š" }, - { "š", "š" }, - { "Ÿ", "Ÿ" }, - { "ˆ", "ˆ" }, - { "˜", "˜" }, - { " ", " " }, - { " ", " " }, - { " ", " " }, - { "‌", "‌" }, - { "‍", "‍" }, - { "‎", "‎" }, - { "‏", "‏" }, - { "–", "–" }, - { "—", "—" }, - { "‘", "‘" }, - { "’", "’" }, - { "‚", "‚" }, - { "“", "“" }, - { "”", "”" }, - { "„", "„" }, - { "†", "†" }, - { "‡", "‡" }, - { "‰", "‰" }, - { "‹", "‹" }, - { "›", "›" }, - { "€", "€" }, - { "ƒ", "ƒ" }, - { "Α", "Α" }, - { "Β", "Β" }, - { "Γ", "Γ" }, - { "Δ", "Δ" }, - { "Ε", "Ε" }, - { "Ζ", "Ζ" }, - { "Η", "Η" }, - { "Θ", "Θ" }, - { "Ι", "Ι" }, - { "Κ", "Κ" }, - { "Λ", "Λ" }, - { "Μ", "Μ" }, - { "Ν", "Ν" }, - { "Ξ", "Ξ" }, - { "Ο", "Ο" }, - { "Π", "Π" }, - { "Ρ", "Ρ" }, - { "Σ", "Σ" }, - { "Τ", "Τ" }, - { "Υ", "Υ" }, - { "Φ", "Φ" }, - { "Χ", "Χ" }, - { "Ψ", "Ψ" }, - { "Ω", "Ω" }, - { "α", "α" }, - { "β", "β" }, - { "γ", "γ" }, - { "δ", "δ" }, - { "ε", "ε" }, - { "ζ", "ζ" }, - { "η", "η" }, - { "θ", "θ" }, - { "ι", "ι" }, - { "κ", "κ" }, - { "λ", "λ" }, - { "μ", "μ" }, - { "ν", "ν" }, - { "ξ", "ξ" }, - { "ο", "ο" }, - { "π", "π" }, - { "ρ", "ρ" }, - { "ς", "ς" }, - { "σ", "σ" }, - { "τ", "τ" }, - { "υ", "υ" }, - { "φ", "φ" }, - { "χ", "χ" }, - { "ψ", "ψ" }, - { "ω", "ω" }, - { "ϑ", "ϑ" }, - { "ϒ", "ϒ" }, - { "ϖ", "ϖ" }, - { "•", "•" }, - { "…", "…" }, - { "′", "′" }, - { "″", "″" }, - { "‾", "‾" }, - { "⁄", "⁄" }, - { "℘", "℘" }, - { "ℑ", "ℑ" }, - { "ℜ", "ℜ" }, - { "™", "™" }, - { "ℵ", "ℵ" }, - { "←", "←" }, - { "↑", "↑" }, - { "→", "→" }, - { "↓", "↓" }, - { "↔", "↔" }, - { "↵", "↵" }, - { "⇐", "⇐" }, - { "⇑", "⇑" }, - { "⇒", "⇒" }, - { "⇓", "⇓" }, - { "⇔", "⇔" }, - { "∀", "∀" }, - { "∂", "∂" }, - { "∃", "∃" }, - { "∅", "∅" }, - { "∇", "∇" }, - { "∈", "∈" }, - { "∉", "∉" }, - { "∋", "∋" }, - { "∏", "∏" }, - { "∑", "∑" }, - { "−", "−" }, - { "∗", "∗" }, - { "√", "√" }, - { "∝", "∝" }, - { "∞", "∞" }, - { "∠", "∠" }, - { "∧", "∧" }, - { "∨", "∨" }, - { "∩", "∩" }, - { "∪", "∪" }, - { "∫", "∫" }, - { "∴", "∴" }, - { "∼", "∼" }, - { "≅", "≅" }, - { "≈", "≈" }, - { "≠", "≠" }, - { "≡", "≡" }, - { "≤", "≤" }, - { "≥", "≥" }, - { "⊂", "⊂" }, - { "⊃", "⊃" }, - { "⊄", "⊄" }, - { "⊆", "⊆" }, - { "⊇", "⊇" }, - { "⊕", "⊕" }, - { "⊗", "⊗" }, - { "⊥", "⊥" }, - { "⋅", "⋅" }, - { "⌈", "⌈" }, - { "⌉", "⌉" }, - { "⌊", "⌊" }, - { "⌋", "⌋" }, - { "⟨", "〈" }, - { "⟩", "〉" }, - { "◊", "◊" }, - { "♠", "♠" }, - { "♣", "♣" }, - { "♥", "♥" }, - { "♦", "♦" }, - { " ", " " }, - { "¡", "¡" }, - { "¢", "¢" }, - { "£", "£" }, - { "¤", "¤" }, - { "¥", "¥" }, - { "¦", "¦" }, - { "§", "§" }, - { "¨", "¨" }, - { "©", "©" }, - { "ª", "ª" }, - { "«", "«" }, - { "¬", "¬" }, - { "­", "­" }, - { "®", "®" }, - { "¯", "¯" }, - { "°", "°" }, - { "±", "±" }, - { "²", "²" }, - { "³", "³" }, - { "´", "´" }, - { "µ", "µ" }, - { "¶", "¶" }, - { "·", "·" }, - { "¸", "¸" }, - { "¹", "¹" }, - { "º", "º" }, - { "»", "»" }, - { "¼", "¼" }, - { "½", "½" }, - { "¾", "¾" }, - { "¿", "¿" }, - { "À", "À" }, - { "Á", "Á" }, - { "Â", "Â" }, - { "Ã", "Ã" }, - { "Ä", "Ä" }, - { "Å", "Å" }, - { "Æ", "Æ" }, - { "Ç", "Ç" }, - { "È", "È" }, - { "É", "É" }, - { "Ê", "Ê" }, - { "Ë", "Ë" }, - { "Ì", "Ì" }, - { "Í", "Í" }, - { "Î", "Î" }, - { "Ï", "Ï" }, - { "Ð", "Ð" }, - { "Ñ", "Ñ" }, - { "Ò", "Ò" }, - { "Ó", "Ó" }, - { "Ô", "Ô" }, - { "Õ", "Õ" }, - { "Ö", "Ö" }, - { "×", "×" }, - { "Ø", "Ø" } - }; + resource.Meta ??= new Meta(); + + foreach (var item in SUBSETTED_TAGS) + { + if (!resource.Meta.Tag.Any(t => t.System == item.System && t.Code == item.Code)) + { + resource.Meta.Tag.Add((Coding)item.DeepCopy()); + } + } + } - _xmlReplacements = xr; - return xr; +#pragma warning disable CS0618 // Type or member is obsolete + foreach (var child in instance.Children()) +#pragma warning restore CS0618 // Type or member is obsolete + addSubsetted(child, atRoot: false); } } -} -#nullable restore \ No newline at end of file + private static readonly Coding[] SUBSETTED_TAGS = + [ + new("http://hl7.org/fhir/v3/ObservationValue", "SUBSETTED"), // STU3 Tag + new("http://terminology.hl7.org/CodeSystem/v3-ObservationValue", "SUBSETTED") // Tag from R4 and higher + ]; + + /// + /// Clones an instance, then marks it as subsetted. + /// + internal static Base MakeSubsettedClone(Base source) + { + var clone = (Base)source.DeepCopy(); + MarkSubsetted(clone); + return clone; + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs index 676ac9784a..4610093f16 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs @@ -166,7 +166,7 @@ public void TestConstruction() } [TestMethod] - public async Tasks.Task SuccessfullyCreated() + public void SuccessfullyCreated() { var patient = createPatient(); @@ -179,7 +179,7 @@ public async Tasks.Task SuccessfullyCreated() pat.ActiveElement.SetStringExtension("urn:2", "world!"); pat.Identifier.Add(new Identifier("http://nu.nl", "1234567")); pat.Identifier.Add(new Identifier("http://toen.nl", "7654321")); - XmlAssert.AreSame("in place", await pat.ToXmlAsync(), await patient.ToXmlAsync()); + XmlAssert.AreSame("in place", pat.ToXml(), patient.ToXml()); } [TestMethod] @@ -409,4 +409,4 @@ public async Task ResolveByCanonicalUriAsync(string uri) } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeOnBaseTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeOnBaseTests.cs index 918dc757b6..e08d84e496 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeOnBaseTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeOnBaseTests.cs @@ -29,7 +29,7 @@ public void SetupSource() var bundle = (new FhirXmlParser()).Parse(bundleXml); Assert.IsNotNull(bundle); - _bundleNode = bundle.ToElementNode(); + _bundleNode = bundle.ToPocoNode(); } [TestMethod] @@ -44,15 +44,15 @@ public void GetContainedAndBundledResources() Assert.AreEqual("urn:uuid:04121321-4af5-424c-a0e1-ed3aab1c349d", entries[1].FullUrl); Assert.AreEqual("http://example.org/fhir/Patient/b", entries[3].FullUrl); - Assert.IsFalse(entries[1].Resource!.ToElementNode().ContainedResources().Any()); - Assert.IsNotNull(entries[1].Resource!.ToElementNode().Children().First()); + Assert.IsFalse(entries[1].Resource!.ToPocoNode().ContainedResources().Any()); + Assert.IsNotNull(entries[1].Resource!.ToPocoNode().Children().First()); Assert.AreEqual("a", entries[2].Resource!.Id); var entry6 = entries[6].Resource; - Assert.AreEqual(2, entry6!.ToElementNode().ContainedResources().Count()); - Assert.IsFalse(entry6.ToElementNode().BundledResources().Any()); - Assert.AreEqual("orgY", (entry6.ToElementNode().ContainedResources().Skip(1).First().Children("id").First().Value)); + Assert.AreEqual(2, entry6!.ToPocoNode().ContainedResources().Count()); + Assert.IsFalse(entry6.ToPocoNode().BundledResources().Any()); + Assert.AreEqual("orgY", (entry6.ToPocoNode().ContainedResources().Skip(1).First().Children("id").First().Value)); } [TestMethod] diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs index dc5f84bc7b..d9c28ac5df 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ScopedNodeTests.cs @@ -278,7 +278,7 @@ public void Bundle_WithEntryWithoutFullUrl_ShouldNotThrow() { var bundle = new Bundle() { Type = Bundle.BundleType.Batch, Entry = [new Bundle.EntryComponent() { Resource = new Patient() }]}; - var enumerate = () => bundle.ToElementNode().BundledResources(); + var enumerate = () => bundle.ToPocoNode().BundledResources(); enumerate.Should().NotThrow().Subject.Should().ContainSingle(c => !c.Children("fullUrl").Any()); } diff --git a/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml index b648e6f903..559447683e 100644 --- a/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml @@ -1,6 +1,13 @@  + + CP0001 + T:Hl7.Fhir.ElementModel.TypedElementExtensions + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0001 T:Hl7.Fhir.Model.ModelInfo.SearchParamComponent @@ -36,6 +43,209 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForCdsHooks(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJObject(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJson(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXDocument(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXml(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonSerializer + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlSerializer + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0011 F:Hl7.Fhir.Model.Questionnaire.QuestionnaireItemType.Attachment @@ -134,6 +344,13 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP1002 + Hl7.Fhir.Base, Version=6.0.0.0, Culture=neutral, PublicKeyToken=d706911480550fc3 + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + PKV006 .NETStandard,Version=v2.0 diff --git a/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml index fdfa7f243f..7b94a45083 100644 --- a/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml @@ -1,6 +1,13 @@  + + CP0001 + T:Hl7.Fhir.ElementModel.TypedElementExtensions + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + CP0001 T:Hl7.Fhir.Model.Citation.CitedArtifactContributorshipSummaryComponent @@ -43,6 +50,216 @@ lib/net8.0/Hl7.Fhir.R4B.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForCdsHooks(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJObject(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJson(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXDocument(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXml(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonSerializer + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlSerializer + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP1002 + Hl7.Fhir.Base, Version=6.0.0.0, Culture=neutral, PublicKeyToken=d706911480550fc3 + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + PKV006 .NETStandard,Version=v2.0 diff --git a/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml index 40dc6f0abd..480d1cc528 100644 --- a/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml @@ -1,6 +1,13 @@  + + CP0001 + T:Hl7.Fhir.ElementModel.TypedElementExtensions + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + CP0001 T:Hl7.Fhir.Model.Citation.CitedArtifactContributorshipSummaryComponent @@ -50,6 +57,216 @@ lib/net8.0/Hl7.Fhir.R5.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForCdsHooks(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJObject(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJson(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXDocument(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXml(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonSerializer + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlSerializer + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP1002 + Hl7.Fhir.Base, Version=6.0.0.0, Culture=neutral, PublicKeyToken=d706911480550fc3 + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + PKV006 .NETStandard,Version=v2.0 diff --git a/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs b/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs index d7749025fd..f678de2fe2 100644 --- a/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs +++ b/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs @@ -31,7 +31,7 @@ public async Task CheckCopyAllFields() var p = await new FhirXmlParser().ParseAsync(xml); var p2 = (Patient)p.DeepCopy(); - var xml2 = await new FhirXmlSerializer().SerializeToStringAsync(p2); + var xml2 = new FhirXmlSerializer().SerializeToString(p2); XmlAssert.AreSame("TestPatient.xml", xml, xml2); } @@ -42,7 +42,7 @@ public async Task CheckCopyCarePlan() var p = await new FhirXmlParser().ParseAsync(xml); var p2 = (CarePlan)p.DeepCopy(); - var xml2 = await new FhirXmlSerializer().SerializeToStringAsync(p2); + var xml2 = new FhirXmlSerializer().SerializeToString(p2); XmlAssert.AreSame("careplan-example-f201-renal.xml", xml, xml2); } @@ -78,4 +78,4 @@ public void CheckCopyPerformance() } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs index 9377860df1..66aaedfe12 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs @@ -162,7 +162,7 @@ public async Tasks.Task RetainSpacesInAttribute() Assert.IsTrue(basic.GetStringExtension("http://blabla.nl").Contains("\n")); - var outp = await FhirXmlSerializer.SerializeToStringAsync(basic); + var outp = FhirXmlSerializer.SerializeToString(basic); Assert.IsTrue(outp.Contains(" ")); } @@ -260,17 +260,17 @@ public async Tasks.Task EdgecaseRoundtrip() var poco = await FhirJsonParser.ParseAsync(json); Assert.IsNotNull(poco); - var xml = await FhirXmlSerializer.SerializeToStringAsync(poco); + var xml = FhirXmlSerializer.SerializeToString(poco); Assert.IsNotNull(xml); await File.WriteAllTextAsync(Path.Combine(tempPath, "edgecase.xml"), xml); poco = await FhirXmlParser.ParseAsync(xml); Assert.IsNotNull(poco); - var json2 = await FhirJsonSerializer.SerializeToStringAsync(poco); + var json2 = FhirJsonSerializer.SerializeToString(poco); Assert.IsNotNull(json2); await File.WriteAllTextAsync(Path.Combine(tempPath, "edgecase.json"), json2); - List errors = new List(); + List errors = []; JsonAssert.AreSame("edgecase.json", json, json2, errors); Console.WriteLine(String.Join("\r\n", errors)); Assert.AreEqual(0, errors.Count, "Errors were encountered comparing converted content"); @@ -284,12 +284,12 @@ public async Tasks.Task ContainedBaseIsNotAddedToId() o.Contained.Add(p); o.ResourceBase = new Uri("http://nu.nl/fhir"); - var xml = await FhirXmlSerializer.SerializeToStringAsync(o); + var xml = FhirXmlSerializer.SerializeToString(o); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); var o2 = await FhirXmlParser.ParseAsync(xml); o2.ResourceBase = new Uri("http://nu.nl/fhir"); - xml = await FhirXmlSerializer.SerializeToStringAsync(o2); + xml = FhirXmlSerializer.SerializeToString(o2); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); } @@ -308,7 +308,7 @@ public async Tasks.Task EmptyRoundTrip() } }; - var json = await FhirJsonSerializer.SerializeToStringAsync(patient); + var json = FhirJsonSerializer.SerializeToString(patient); var parsedPatient = await FhirJsonParser.ParseAsync(json); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); @@ -325,7 +325,7 @@ public async Tasks.Task EmptyRoundTrip() } } - var xml = await FhirXmlSerializer.SerializeToStringAsync(patient); + var xml = FhirXmlSerializer.SerializeToString(patient); parsedPatient = await FhirXmlParser.ParseAsync(xml); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); @@ -352,9 +352,9 @@ public async Tasks.Task SerializeNarrativeWithQuotes() Text = new Narrative() { Div = "
Nasty, a text with both \"double\" quotes and 'single' quotes
" } }; - var xml = await FhirXmlSerializer.SerializeToStringAsync(p); + var xml = FhirXmlSerializer.SerializeToString(p); Assert.IsNotNull(await FhirXmlParser.ParseAsync(xml)); - var json = await FhirJsonSerializer.SerializeToStringAsync(p); + var json = FhirJsonSerializer.SerializeToString(p); Assert.IsNotNull(await FhirJsonParser.ParseAsync(json)); } @@ -406,4 +406,4 @@ public async Tasks.Task ParseBinaryForR4andHigherWithUnknownSTU3Element() await act.Should().ThrowAsync(); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs index e73f5d599f..cf42572890 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs @@ -29,25 +29,25 @@ public class SerializationTests private readonly Meta metaPoco = new Meta { LastUpdated = new DateTimeOffset(2014, 12, 24, 16, 30, 56, 31, new TimeSpan(1, 0, 0)), VersionId = "3141" }; [TestMethod] - public async Tasks.Task SerializeMetaXml() + public void SerializeMetaXml() { - var xml = await new FhirXmlSerializer().SerializeToStringAsync(metaPoco, root: "meta"); + var xml = new FhirXmlSerializer().SerializeToString(metaPoco, rootName: "meta"); Assert.AreEqual(metaXml, xml); } [TestMethod] - public async Tasks.Task SerializeMetaJson() + public void SerializeMetaJson() { - var json = await new FhirJsonSerializer().SerializeToStringAsync(metaPoco); - Assert.AreEqual(metaJson, json); + var json = new FhirJsonSerializer().SerializeToString(metaPoco); + JsonAssert.AreSame("0", metaJson, json); } [TestMethod] public async Tasks.Task ParseMetaXml() { var poco = (Meta)(await new FhirXmlParser().ParseAsync(metaXml, typeof(Meta))); - var xml = await new FhirXmlSerializer().SerializeToStringAsync(poco, root: "meta"); + var xml = new FhirXmlSerializer().SerializeToString(poco, rootName: "meta"); Assert.IsTrue(poco.IsExactly(metaPoco)); Assert.AreEqual(metaXml, xml); @@ -73,10 +73,10 @@ public void ParsePatientXmlNullType() public async Tasks.Task ParseMetaJson() { var poco = (Meta)await (new FhirJsonParser().ParseAsync(metaJson, typeof(Meta))); - var json = await FhirJsonSerializer.SerializeToStringAsync(poco); + var json = FhirJsonSerializer.SerializeToString(poco); Assert.IsTrue(poco.IsExactly(metaPoco)); - Assert.AreEqual(metaJson, json); + JsonAssert.AreSame("0", metaJson, json); } [TestMethod] @@ -93,22 +93,22 @@ public async Tasks.Task ParsePatientJsonNullType() } [TestMethod] - public async Tasks.Task AvoidBOMUse() + public void AvoidBOMUse() { Bundle b = new Bundle() { Total = 1000 }; - var data = await FhirJsonSerializer.SerializeToBytesAsync(b); + var data = FhirJsonSerializer.SerializeToBytes(b); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); - data = await FhirXmlSerializer.SerializeToBytesAsync(b); + data = FhirXmlSerializer.SerializeToBytes(b); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); - Patient p = new Patient() { Active = true }; + Patient p = new() { Active = true }; - data = await FhirJsonSerializer.SerializeToBytesAsync(p); + data = FhirJsonSerializer.SerializeToBytes(p); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); - data = await FhirXmlSerializer.SerializeToBytesAsync(p); + data = FhirXmlSerializer.SerializeToBytes(p); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); } @@ -131,56 +131,15 @@ public void TestProbing() private readonly FhirXmlParser FhirXmlParser = new FhirXmlParser(); private readonly FhirJsonParser FhirJsonParser = new FhirJsonParser(); - - //[TestMethod] - //public void HandleCommentsJson() - //{ - // string json = TestDataHelper.ReadTestData("TestPatient.json"); - - // var pat = FhirJsonParser.Parse(json); - - // Assert.AreEqual(1, pat.Telecom[0].FhirCommentsElement.Count); - // Assert.AreEqual(" home communication details aren't known ", pat.Telecom[0].FhirComments.First()); - - // pat.Telecom[0].FhirCommentsElement.Add(new FhirString("A second line")); - - // json = FhirJsonSerializer.SerializeToString(pat); - // pat = FhirJsonParser.Parse(json); - - // Assert.AreEqual(2, pat.Telecom[0].FhirCommentsElement.Count); - // Assert.AreEqual(" home communication details aren't known ", pat.Telecom[0].FhirComments.First()); - // Assert.AreEqual("A second line", pat.Telecom[0].FhirComments.Skip(1).First()); - //} - - //[TestMethod, Ignore] - //public void HandleCommentsXml() - //{ - // string xml = TestDataHelper.ReadTestData("TestPatient.xml"); - - // var pat = FhirXmlParser.Parse(xml); - - // Assert.AreEqual(1, pat.Name[0].FhirCommentsElement.Count); - // Assert.AreEqual("See if this is roundtripped", pat.Name[0].FhirComments.First()); - - // pat.Name[0].FhirCommentsElement.Add(new FhirString("A second line")); - - // xml = FhirXmlSerializer.SerializeToString(pat); - - // Assert.AreEqual(2, pat.Name[0].FhirCommentsElement.Count); - // Assert.AreEqual("See if this is roundtripped", pat.Name[0].FhirComments.First()); - // Assert.AreEqual("A second line", pat.Name[0].FhirComments.Skip(1).First()); - //} - - [TestMethod] - public async Tasks.Task BundleLinksUnaltered() + public void BundleLinksUnaltered() { var b = new Bundle { NextLink = new Uri("Organization/123456/_history/123456", UriKind.Relative) }; - var xml = await new FhirXmlSerializer().SerializeToStringAsync(b); + var xml = new FhirXmlSerializer().SerializeToString(b); b = FhirXmlParser.Parse(xml); @@ -188,7 +147,7 @@ public async Tasks.Task BundleLinksUnaltered() } [TestMethod] - public async Tasks.Task TestDecimalPrecisionSerializationInJson() + public void TestDecimalPrecisionSerializationInJson() { var dec6 = 6m; var dec60 = 6.0m; @@ -196,8 +155,8 @@ public async Tasks.Task TestDecimalPrecisionSerializationInJson() var obs = new Observation(); obs.AddExtension("http://example.org/DecimalPrecision", ext); - var json = await FhirJsonSerializer.SerializeToStringAsync(obs); - var obs2 = await FhirJsonParser.ParseAsync(json); + var json = FhirJsonSerializer.SerializeToString(obs); + var obs2 = FhirJsonParser.Parse(json); Assert.AreEqual("6", ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); @@ -205,8 +164,8 @@ public async Tasks.Task TestDecimalPrecisionSerializationInJson() obs = new Observation(); obs.AddExtension("http://example.org/DecimalPrecision", ext); - json = await FhirJsonSerializer.SerializeToStringAsync(obs); - obs2 = await FhirJsonParser.ParseAsync(json); + json = FhirJsonSerializer.SerializeToString(obs); + obs2 = FhirJsonParser.Parse(json); Assert.AreEqual("6.0", ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); } @@ -219,7 +178,7 @@ public async Tasks.Task TestLongDecimalSerialization() var obs = new Observation(); obs.AddExtension("http://example.org/DecimalPrecision", ext); - var json = await FhirJsonSerializer.SerializeToStringAsync(obs); + var json = FhirJsonSerializer.SerializeToString(obs); var obs2 = await FhirJsonParser.ParseAsync(json); Assert.AreEqual(dec.ToString(CultureInfo.InvariantCulture), ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); @@ -231,7 +190,7 @@ public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() var dec6 = 6m; var ext = new FhirDecimal(dec6); var obs = new Observation { Value = new FhirDecimal(dec6) }; - var json = await FhirJsonSerializer.SerializeToStringAsync(obs); + var json = FhirJsonSerializer.SerializeToString(obs); try { var obs2 = await FhirJsonParser.ParseAsync(json); @@ -244,7 +203,7 @@ public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() } [TestMethod] - public async Tasks.Task TestSerializeWhitespacesInXml() + public void TestSerializeWhitespacesInXml() { var patient = new Patient { @@ -254,26 +213,20 @@ public async Tasks.Task TestSerializeWhitespacesInXml() } }; - var trimmed = await FhirXmlSerializer.SerializeToStringAsync(patient); + var trimmed = FhirXmlSerializer.SerializeToString(patient); Assert.IsFalse(trimmed.Contains(" Smith")); Assert.IsFalse(trimmed.Contains("Smith ")); Assert.IsTrue(trimmed.Contains("\"Smith\"")); - - var notTrimmed = await new FhirXmlSerializer(new SerializerSettings { TrimWhiteSpacesInXml = false }).SerializeToStringAsync(patient); - Assert.IsTrue(notTrimmed.Contains(" Smith")); - Assert.IsTrue(notTrimmed.Contains("Smith ")); - Assert.IsFalse(notTrimmed.Contains("\"Smith\"")); - } [TestMethod] - public async Tasks.Task TryScriptInject() + public void TryScriptInject() { var x = new Patient(); x.Name.Add(HumanName.ForFamily("")); - var xml = await FhirXmlSerializer.SerializeToStringAsync(x); + var xml = FhirXmlSerializer.SerializeToString(x); Assert.IsFalse(xml.Contains("(xml); - string outp = await FhirXmlSerializer.SerializeToStringAsync(p); + string outp = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(outp.Contains("\"male\"")); // Pollute the data with an incorrect administrative gender p.GenderElement.ObjectValue = "superman"; - outp = await FhirXmlSerializer.SerializeToStringAsync(p); + outp = FhirXmlSerializer.SerializeToString(p); Assert.IsFalse(outp.Contains("\"male\"")); Assert.IsTrue(outp.Contains("\"superman\"")); } [TestMethod] - public async Tasks.Task TestNullExtensionRemoval() + public void TestNullExtensionRemoval() { var p = new Patient { @@ -336,62 +289,33 @@ public async Tasks.Task TestNullExtensionRemoval() null }, - Contact = new List - { + Contact = + [ null, - new Patient.ContactComponent { Name = HumanName.ForFamily("Kramer") }, - } + new Patient.ContactComponent { Name = HumanName.ForFamily("Kramer") } + ] }; - var xml = await FhirXmlSerializer.SerializeToStringAsync(p); + var xml = FhirXmlSerializer.SerializeToString(p); - var p2 = await (new FhirXmlParser()).ParseAsync(xml); + var p2 = (new FhirXmlParser()).Parse(xml); Assert.AreEqual(1, p2.Extension.Count); Assert.AreEqual(1, p2.Contact.Count); } - //An empty object is not allowed - //[TestMethod] - //public void SerializeEmptyParams() - //{ - // var par = new Parameters(); - // var xml = FhirXmlSerializer.SerializeToString(par); - - // var par2 = (new FhirXmlParser()).Parse(xml); - // Assert.AreEqual(0, par2.Parameter.Count); - //} - [TestMethod] public async Tasks.Task SerializeJsonWithPlainDiv() { - // var res = new ValueSet() { Url = "http://example.org/fhir/ValueSet/MyValueSetExample" }; - - string json = TestDataHelper.ReadTestData(@"valueset-v2-0717.json"); + string json = TestDataHelper.ReadTestData(@"TestPatient.json"); Assert.IsNotNull(json); var parser = new FhirJsonParser { Settings = { PermissiveParsing = true } }; - var vs = await parser.ParseAsync(json); - Assert.IsNotNull(vs); + var pat = await parser.ParseAsync(json); + Assert.IsNotNull(pat); - var xml = await FhirXmlSerializer.SerializeToStringAsync(vs); + var xml = FhirXmlSerializer.SerializeToString(pat); Assert.IsNotNull(xml); } - [TestMethod] - public async Tasks.Task TestClaimJsonSerialization() - { - var c = new Claim(); - c.Payee = new Claim.PayeeComponent(); - c.Payee.Type = new CodeableConcept(null, "test"); - c.Payee.ResourceType = new Coding(null, "test2"); - c.Payee.Party = new ResourceReference("Practitioner/example", "Example, Dr John"); - - string json = await FhirJsonSerializer.SerializeToStringAsync(c); - var c2 = await new FhirJsonParser().ParseAsync(json); - Assert.AreEqual("test", c2.Payee.Type.Coding[0].Code); - Assert.AreEqual("test2", c2.Payee.ResourceType.Code); - Assert.AreEqual("Practitioner/example", c2.Payee.Party.Reference); - } - [FhirType("Bundle")] //[DataContract] public class CustomBundle : Bundle @@ -402,7 +326,7 @@ public CustomBundle() : base() { } // [WMR 20170825] Richard Kavanagh: runtime exception while serializating derived PoCo classes // Workaround: add the FhirType attribute to derived class [TestMethod] - public async Tasks.Task TestDerivedPoCoSerialization() + public void TestDerivedPoCoSerialization() { ModelInfo.ModelInspector.ImportType(typeof(CustomBundle)); @@ -412,10 +336,10 @@ public async Tasks.Task TestDerivedPoCoSerialization() Id = "MyBundle" }; - var xml = await FhirXmlSerializer.SerializeToStringAsync(bundle); + var xml = FhirXmlSerializer.SerializeToString(bundle); Assert.IsNotNull(xml); - var json = await FhirJsonSerializer.SerializeToStringAsync(bundle); + var json = FhirJsonSerializer.SerializeToString(bundle); Assert.IsNotNull(json); } @@ -431,14 +355,16 @@ public void TestSerializeToXmlDocument() Text = new Narrative { Status = Narrative.NarrativeStatus.Generated, Div = "
A great blues player
" }, Meta = new Meta { ElementId = "eric-clapton", VersionId = "1234" }, - Name = new List { new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official } }, + Name = [new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official }], Active = true, BirthDate = "2015-07-09", Gender = AdministrativeGender.Male }; +#pragma warning disable CS0618 // Type or member is obsolete var doc = FhirXmlSerializer.SerializeToDocument(patientOne); +#pragma warning restore CS0618 // Type or member is obsolete Assert.IsNotNull(doc); var root = doc.Root; @@ -450,7 +376,7 @@ public void TestSerializeToXmlDocument() // [WMR 20180409] NEW: Serialize to JObject [TestMethod] - public async Tasks.Task TestSerializeToJsonDocument() + public void TestSerializeToJsonDocument() { // Note: output order is defined by core resource/datatype definitions! @@ -466,13 +392,13 @@ public async Tasks.Task TestSerializeToJsonDocument() }; var serializer = new FhirJsonSerializer(); - var jsonText = await serializer.SerializeToStringAsync(patientOne); + var jsonText = serializer.SerializeToString(patientOne); Assert.IsNotNull(jsonText); var doc = JObject.Parse(jsonText); Assert.AreEqual(8, doc.Count); // Including resourceType - JToken assertProperty(JToken t, string expectedName) + static JToken assertProperty(JToken t, string expectedName) { Assert.AreEqual(JTokenType.Property, t.Type); var p = t as JProperty; @@ -481,7 +407,7 @@ JToken assertProperty(JToken t, string expectedName) return t; } - JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expectedType, object expectedValue) + static JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expectedType, object expectedValue) { var p = t as JProperty; Assert.IsNotNull(p); @@ -493,7 +419,8 @@ JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expecte return t; } - JToken assertStringProperty(JToken t, string expectedName, object expectedValue) => assertPrimitiveProperty(t, expectedName, JTokenType.String, expectedValue); + static JToken assertStringProperty(JToken t, string expectedName, object expectedValue) => + assertPrimitiveProperty(t, expectedName, JTokenType.String, expectedValue); Assert.AreEqual(JTokenType.Property, doc.First.Type); var token = assertStringProperty(doc.First, "resourceType", "Patient"); @@ -529,18 +456,18 @@ JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expecte /// This test proves issue 583: https://github.com/FirelyTeam/firely-net-sdk/issues/583 /// [TestMethod] - public async Tasks.Task SummarizeSerializingTest() + public void SummarizeSerializingTest() { var patient = new Patient(); var telecom = new ContactPoint(ContactPoint.ContactPointSystem.Phone, ContactPoint.ContactPointUse.Work, "0471 144 099"); telecom.AddExtension("http://healthconnex.com.au/hcxd/Phone/IsMain", new FhirBoolean(true)); patient.Telecom.Add(telecom); - var doc = await FhirXmlSerializer.SerializeToStringAsync(patient, Fhir.Rest.SummaryType.True); + var doc = FhirXmlSerializer.SerializeToString(patient, Fhir.Rest.SummaryType.True); Assert.IsFalse(doc.Contains("(json); Assert.IsTrue(patient.IsExactly(res), "1"); @@ -562,8 +489,8 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() Assert.IsTrue(patient.IsExactly(res), "2"); // Is the serialization still correct without milliseconds? - var json2 = await new FhirJsonSerializer().SerializeToStringAsync(patient); - Assert.AreEqual(json, json2, "3"); + var json2 = new FhirJsonSerializer().SerializeToString(patient); + JsonAssert.AreSame("3", json, json2); // Is the parsing still correct with a few milliseconds and TimeZone? patient = new Patient { Meta = new Meta { LastUpdated = new DateTimeOffset(2018, 8, 13, 13, 41, 56, 12, TimeSpan.Zero) } }; @@ -572,8 +499,8 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() Assert.IsTrue(patient.IsExactly(res), "4"); // Is the serialization still correct with a few milliseconds? - json2 = await new FhirJsonSerializer().SerializeToStringAsync(patient); - Assert.AreEqual(json, json2, "5"); + json2 = new FhirJsonSerializer().SerializeToString(patient); + JsonAssert.AreSame("5", json, json2); } [TestMethod] @@ -588,7 +515,7 @@ public async Tasks.Task SerializerHandlesEmptyChildObjects() poco.Meta = new Meta(); - var reserialized = await poco.ToJsonAsync(); + var reserialized = poco.ToJson(); var newPoco = await fhirJsonParser.ParseAsync(reserialized); @@ -605,14 +532,17 @@ public void IncludeMandatoryInElementsSummaryTest() }; // default behavior - var json = new FhirJsonSerializer().SerializeToDocument(obs, elements: new[] { "issued" }); - +#pragma warning disable CS0618 // Type or member is obsolete + var json = new FhirJsonSerializer().SerializeToDocument(obs, elements: ["issued"]); +#pragma warning restore CS0618 // Type or member is obsolete Assert.IsTrue(json.ContainsKey("issued")); Assert.IsFalse(json.ContainsKey("status")); // Adding mandatory elements to the set of elements - json = new FhirJsonSerializer(new SerializerSettings() { IncludeMandatoryInElementsSummary = true }) - .SerializeToDocument(obs, elements: new[] { "issued" }); +#pragma warning disable CS0618 // Type or member is obsolete + json = new FhirJsonSerializer() + .SerializeToDocument(obs, elements: ["issued"], includeMandatoryInElementsSummary: true); +#pragma warning restore CS0618 // Type or member is obsolete Assert.IsTrue(json.ContainsKey("issued")); Assert.IsTrue(json.ContainsKey("status")); @@ -621,17 +551,14 @@ public void IncludeMandatoryInElementsSummaryTest() [TestMethod] public void AllowParseDateWithDateTime() { - var patientJson = "{ \"resourceType\": \"Patient\", \"birthDate\": \"1991-02-03T11:22:33Z\" }"; - var parser = new FhirJsonParser(); - - // Assert.ThrowsException(() => parser.Parse(patientJson)); + const string patientJson = "{ \"resourceType\": \"Patient\", \"birthDate\": \"1991-02-03T11:22:33Z\" }"; #pragma warning disable CS0618 // Type or member is obsolete - parser = new FhirJsonParser(new ParserSettings { TruncateDateTimeToDate = true }); + var parser = new FhirJsonParser(new ParserSettings { TruncateDateTimeToDate = true }); #pragma warning restore CS0618 // Type or member is obsolete var patient = parser.Parse(patientJson); - Assert.AreEqual("1991-02-03", patient.BirthDate.ToString()); + Assert.AreEqual("1991-02-03", patient.BirthDate); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/SummarySerializationTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/SummarySerializationTests.cs index 245c391f9a..2b9b53b1b2 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/SummarySerializationTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/SummarySerializationTests.cs @@ -11,12 +11,14 @@ using Hl7.Fhir.Rest; using Hl7.Fhir.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Xml.Linq; using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Tests.Serialization @@ -35,7 +37,7 @@ public void TestConstructSystemTextJsonSerializer() var p = new Patient { BirthDate = "1972-11-30", // present in both summary and full - Photo = new List() { new Attachment() { ContentType = "text/plain" } } + Photo = [new Attachment() { ContentType = "text/plain" }] }; @@ -49,33 +51,37 @@ public async Tasks.Task TestSummary() var p = new Patient { BirthDate = "1972-11-30", // present in both summary and full - Photo = new List() { new Attachment() { ContentType = "text/plain" } } + Photo = [new Attachment() { ContentType = "text/plain" }] }; - var full = await FhirXmlSerializer.SerializeToStringAsync(p); + var full = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(full.Contains("Test Questionnaire
" }; - q.Status = PublicationStatus.Active; - q.Date = "2015-09-27"; - q.Title = "TITLE"; - q.Item = new List(); - q.Item.Add(new Questionnaire.ItemComponent() + var q = new Questionnaire { - LinkId = "linkid", - Text = "TEXT" - }); + Text = new Narrative() + { + Div = "
Test Questionnaire
" + }, + Status = PublicationStatus.Active, + Date = "2015-09-27", + Title = "TITLE", + Item = + [ + new Questionnaire.ItemComponent() { LinkId = "linkid", Text = "TEXT" } + + ] + }; Assert.IsNull(q.Meta, "Meta element has not been created."); - var qfull = await FhirXmlSerializer.SerializeToStringAsync(q); + var qfull = FhirXmlSerializer.SerializeToString(q); Assert.IsNull(q.Meta, "Meta element should not be introduced here."); Console.WriteLine("summary: Fhir.Rest.SummaryType.False"); Console.WriteLine(qfull); @@ -86,7 +92,7 @@ public async Tasks.Task TestSummary() Assert.IsTrue(qfull.Contains(" t.System == "http://hl7.org/fhir/v3/ObservationValue" && t.Code == "SUBSETTED").Count(), "Subsetted Tag should not still be there."); + Assert.AreEqual(0, q.Meta.Tag.Count(t => t.System == "http://hl7.org/fhir/v3/ObservationValue" && t.Code == "SUBSETTED"), "Subsetted Tag should not still be there."); // Verify that reloading the content into an object... // make sure we accept the crappy output with empty groups var nav = await FhirXmlNode.ParseAsync(qText, new FhirXmlParsingSettings { PermissiveParsing = true }); var qInflate = FhirXmlParser.Parse(nav); - Assert.AreEqual(1, qInflate.Meta.Tag.Where(t => t.System == "http://hl7.org/fhir/v3/ObservationValue" && t.Code == "SUBSETTED").Count(), "Subsetted Tag should not still be there."); + Assert.AreEqual(1, qInflate.Meta.Tag.Count(t => t.System == "http://hl7.org/fhir/v3/ObservationValue" && t.Code == "SUBSETTED"), "Subsetted Tag should not still be there."); } [TestMethod] - public async Tasks.Task TestIncludeMandatory() + public void TestIncludeMandatory() { - var l = new Library(); - l.Type = new CodeableConcept { TextElement = new FhirString("testMandatoryComplexType") }; - l.Id = "testId"; - l.Language = "testLang"; - var summaryElements = await FhirXmlSerializer.SerializeToStringAsync(l, Fhir.Rest.SummaryType.Count); + var l = new Library + { + Type = new CodeableConcept { TextElement = new FhirString("testMandatoryComplexType") }, Id = "testId", + Language = "testLang" + }; + var summaryElements = FhirXmlSerializer.SerializeToString(l, Fhir.Rest.SummaryType.Count); Assert.IsFalse(summaryElements.Contains("")); @@ -147,7 +154,7 @@ public async Tasks.Task TestIncludeMandatory() PreserveBundle = MaskingNodeSettings.PreserveBundleMode.All }); - var result = await customMaskingNode.ToXmlAsync(settings: new FhirXmlSerializationSettings()); + var result = customMaskingNode.ToXml(); Assert.IsFalse(result.Contains("")); Assert.IsTrue(result.Contains("")); @@ -169,14 +176,14 @@ public async Tasks.Task TestIncludeMandatory() PreserveBundle = MaskingNodeSettings.PreserveBundleMode.None }); - result = await customMaskingNodeForBundle.ToXmlAsync(settings: new FhirXmlSerializationSettings()); + result = customMaskingNodeForBundle.ToXml(); Assert.IsTrue(result.Contains("(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.True, elements: elements)); - await ExceptionAssert.Throws(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.Count, elements: elements)); - await ExceptionAssert.Throws(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.Data, elements: elements)); - await ExceptionAssert.Throws(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.Text, elements: elements)); } [TestMethod] - public async Tasks.Task TestWithMetadata() + public void TestWithMetadata() { var p = new Patient { BirthDate = "1972-11-30" }; - var pSum = await FhirXmlSerializer.SerializeToStringAsync(p, summary: Fhir.Rest.SummaryType.True); + var pSum = FhirXmlSerializer.SerializeToString(p, summary: SummaryType.True); Assert.IsNull(p.Meta, "Meta should not be there"); p.Meta = new Meta { VersionId = "v2" }; // introducing meta data ourselves. - pSum = await FhirXmlSerializer.SerializeToStringAsync(p, summary: Fhir.Rest.SummaryType.True); + pSum = FhirXmlSerializer.SerializeToString(p, summary: Fhir.Rest.SummaryType.True); Assert.IsNotNull(p.Meta, "Meta should still be there"); Assert.AreEqual(0, p.Meta.Tag.Where(t => t.System == "http://hl7.org/fhir/v3/ObservationValue" && t.Code == "SUBSETTED").Count(), "Subsetted Tag should not still be there."); } [TestMethod] - public async Tasks.Task TestBundleSummary() + public void TestBundleSummary() { var p = new Patient { @@ -232,19 +234,19 @@ public async Tasks.Task TestBundleSummary() b.Total = 1; b.Type = Bundle.BundleType.Searchset; - var full = await FhirXmlSerializer.SerializeToStringAsync(b); + var full = FhirXmlSerializer.SerializeToString(b); Assert.IsTrue(full.Contains(" data = new Dictionary - { - { "summary/bundle-summary-true.json", SummaryType.True }, - { "summary/bundle-summary-false.json", SummaryType.False }, - { "summary/bundle-summary-data.json", SummaryType.Data }, - { "summary/bundle-summary-text.json", SummaryType.Text }, - { "summary/bundle-summary-count.json", SummaryType.Count }, - { "summary/bundle-summary-true.xml", SummaryType.True }, - { "summary/bundle-summary-false.xml", SummaryType.False }, - { "summary/bundle-summary-data.xml", SummaryType.Data }, - { "summary/bundle-summary-text.xml", SummaryType.Text }, - { "summary/bundle-summary-count.xml", SummaryType.Count } - }; - var patientOne = new Patient { @@ -276,7 +274,7 @@ public async Tasks.Task TestBundleWithSummaryJson() Text = new Narrative { Div = "
A great blues player
" }, Meta = new Meta { VersionId = "eric-clapton" }, - Name = new List { new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official } }, + Name = [new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official }], Active = true, BirthDate = "2015-07-09", @@ -289,7 +287,7 @@ public async Tasks.Task TestBundleWithSummaryJson() Active = true, Text = new Narrative { Div = "
Another great blues player
", Status = Narrative.NarrativeStatus.Additional }, Meta = new Meta { VersionId = "bb-king" }, - Name = new List { new HumanName { Family = "King", Use = HumanName.NameUse.Nickname } } + Name = [new HumanName { Family = "King", Use = HumanName.NameUse.Nickname }] }; var bundle = new Bundle() @@ -297,70 +295,71 @@ public async Tasks.Task TestBundleWithSummaryJson() Id = "my-bundle", Total = 1803, Type = Bundle.BundleType.Searchset, - Entry = new List { - new Bundle.EntryComponent { Resource = patientOne, FullUrl = "http://base/Patient/patient-one", Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } }, - new Bundle.EntryComponent { Resource = patientTwo, FullUrl = "http://base/Patient/patient-two", Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } } + Entry = + [ + new() + { + Resource = patientOne, + FullUrl = "http://base/Patient/patient-one", + Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } + }, + new() + { + Resource = patientTwo, + FullUrl = "http://base/Patient/patient-two", + Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } } + ] }; - foreach (var pair in data) - { - var expectedFile = pair.Key; - var mode = pair.Value; - - bool inJson = Path.GetExtension(expectedFile) == ".json"; - var actualData = inJson ? await FhirJsonSerializer.SerializeToStringAsync(bundle, mode) : - await FhirXmlSerializer.SerializeToStringAsync(bundle, mode); - var expectedData = TestDataHelper.ReadTestData(expectedFile); - Assert.AreEqual(actualData, expectedData, expectedFile); - } + bool inJson = Path.GetExtension(expectedFile) == ".json"; + var actualData = inJson ? FhirJsonSerializer.SerializeToString(bundle, mode) : + FhirXmlSerializer.SerializeToString(bundle, mode); + var expectedData = TestDataHelper.ReadTestData(expectedFile); + + if(inJson) + JsonAssert.AreSame(expectedFile, expectedData, actualData); + else + XmlAssert.AreSame(expectedFile, XDocument.Parse(expectedData), XDocument.Parse(actualData)); } [TestMethod] - public async Tasks.Task TestResourceWithSummary() + [DataRow("summary/summary-true.json", SummaryType.True)] + [DataRow("summary/summary-false.json", SummaryType.False)] + [DataRow("summary/summary-data.json", SummaryType.Data)] + [DataRow("summary/summary-text.json", SummaryType.Text)] + [DataRow("summary/summary-true.xml", SummaryType.True)] + [DataRow("summary/summary-false.xml", SummaryType.False)] + [DataRow("summary/summary-data.xml", SummaryType.Data)] + [DataRow("summary/summary-text.xml", SummaryType.Text)] + public void TestResourceWithSummary(string expectedFile, SummaryType mode) { - Dictionary data = new Dictionary - { - { "summary/summary-true.json", SummaryType.True }, - { "summary/summary-false.json", SummaryType.False }, - { "summary/summary-data.json", SummaryType.Data }, - { "summary/summary-text.json", SummaryType.Text }, - { "summary/summary-true.xml", SummaryType.True }, - { "summary/summary-false.xml", SummaryType.False }, - { "summary/summary-data.xml", SummaryType.Data }, - { "summary/summary-text.xml", SummaryType.Text } - }; - - foreach (var pair in data) + var patientOne = new Patient { - var expectedFile = pair.Key; - var mode = pair.Value; - - var patientOne = new Patient - { + Id = "patient-one", + Text = new Narrative { Status = Narrative.NarrativeStatus.Generated, Div = "
A great blues player
" }, + Meta = new Meta { ElementId = "eric-clapton", VersionId = "1234" }, - Id = "patient-one", - Text = new Narrative { Status = Narrative.NarrativeStatus.Generated, Div = "
A great blues player
" }, - Meta = new Meta { ElementId = "eric-clapton", VersionId = "1234" }, + Name = [new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official }], - Name = new List { new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official } }, + Active = true, + BirthDate = "2015-07-09", + Gender = AdministrativeGender.Male + }; - Active = true, - BirthDate = "2015-07-09", - Gender = AdministrativeGender.Male - }; + bool inJson = Path.GetExtension(expectedFile) == ".json"; + var actualData = inJson ? FhirJsonSerializer.SerializeToString(patientOne, mode) : + FhirXmlSerializer.SerializeToString(patientOne, mode); + var expectedData = TestDataHelper.ReadTestData(expectedFile); - // Properties with IsSummary == true -> Id, Meta, Active, BirthDate, Gender, Name - bool inJson = Path.GetExtension(expectedFile) == ".json"; - var actualData = inJson ? await FhirJsonSerializer.SerializeToStringAsync(patientOne, mode) : - await FhirXmlSerializer.SerializeToStringAsync(patientOne, mode); - var expectedData = TestDataHelper.ReadTestData(expectedFile); - Assert.AreEqual(expectedData, actualData, $"SummaryType.{mode} in file {pair.Key}"); - } + if(inJson) + JsonAssert.AreSame(expectedFile, expectedData, actualData); + else + XmlAssert.AreSame(expectedFile, XDocument.Parse(expectedData), XDocument.Parse(actualData)); } [TestMethod] - public async Tasks.Task TestIdInSummary() + public void TestIdInSummary() { var p = new Patient { @@ -382,7 +381,7 @@ public async Tasks.Task TestIdInSummary() p.AddExtension("http://example.org/ext", new FhirString("dud")); - var full = await FhirXmlSerializer.SerializeToStringAsync(p); + var full = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(full.Contains("narrative")); Assert.IsTrue(full.Contains("dud")); Assert.IsTrue(full.Contains("temp org")); @@ -391,7 +390,7 @@ public async Tasks.Task TestIdInSummary() Assert.IsTrue(full.Contains(" XmlRoundTripAsync(T resource) where T : Resource var baseTestPath = CreateEmptyDir(); var xmlFile = Path.Combine(baseTestPath, "ObservationWithValueQuantityExample.xml"); - var xml = await new FhirXmlSerializer().SerializeToStringAsync(resource); + var xml = new FhirXmlSerializer().SerializeToString(resource); await File.WriteAllTextAsync(xmlFile, xml); xml = await File.ReadAllTextAsync(xmlFile); @@ -83,7 +83,7 @@ static async Tasks.Task JsonRoundTrip(T resource) where T : Resource var baseTestPath = CreateEmptyDir(); var jsonFile = Path.Combine(baseTestPath, "ObservationWithValueQuantityExample.json"); - var json = await new FhirJsonSerializer().SerializeToStringAsync(resource); + var json = new FhirJsonSerializer().SerializeToString(resource); await File.WriteAllTextAsync(jsonFile, json); json = await File.ReadAllTextAsync(jsonFile); @@ -101,4 +101,4 @@ static string CreateEmptyDir() } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.json b/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.json index 64623a13f0..dc4a889d65 100644 --- a/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.json +++ b/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.json @@ -1 +1 @@ -{"resourceType":"Patient","id":"patient-one","meta":{"id":"eric-clapton","versionId":"1234","tag":[{"system":"http://hl7.org/fhir/v3/ObservationValue","code":"SUBSETTED"},{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationValue","code":"SUBSETTED"}]},"active":true,"name":[{"use":"official","family":"Clapton"}],"gender":"male","birthDate":"2015-07-09"} \ No newline at end of file +{"resourceType":"Patient","id":"patient-one","meta":{"versionId":"1234","tag":[{"system":"http://hl7.org/fhir/v3/ObservationValue","code":"SUBSETTED"},{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationValue","code":"SUBSETTED"}]},"active":true,"name":[{"use":"official","family":"Clapton"}],"gender":"male","birthDate":"2015-07-09"} diff --git a/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.xml b/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.xml index 5cae121e4e..28887c8c73 100644 --- a/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.xml +++ b/src/Hl7.Fhir.STU3.Tests/TestData/summary/summary-true.xml @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml b/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml index 794249f8ca..d2942db438 100644 --- a/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml @@ -1,6 +1,13 @@  + + CP0001 + T:Hl7.Fhir.ElementModel.TypedElementExtensions + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0001 T:Hl7.Fhir.Model.ModelInfo.SearchParamComponent @@ -57,6 +64,209 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoSerializer.#ctor(Hl7.Fhir.Specification.FhirRelease) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlSerializer.#ctor(Hl7.Fhir.Serialization.SerializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForCdsHooks(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.JsonSerializerOptionsExtensions.ForFhir(System.Text.Json.JsonSerializerOptions,Hl7.Fhir.Serialization.FhirJsonPocoSerializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJObject(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJson(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToJsonBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXDocument(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXml(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytes(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.ToXmlBytesAsync(Hl7.Fhir.Model.Base,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteTo(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,Newtonsoft.Json.JsonWriter,Hl7.Fhir.Serialization.FhirJsonSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoSerializationExtensions.WriteToAsync(Hl7.Fhir.Model.Base,System.Xml.XmlWriter,Hl7.Fhir.Serialization.FhirXmlSerializationSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonSerializer + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlSerializer + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0008 T:Hl7.Fhir.Model.IElementList diff --git a/src/Hl7.Fhir.Serialization.R4.Tests/RoundtripSignature.cs b/src/Hl7.Fhir.Serialization.R4.Tests/RoundtripSignature.cs index 5d86eb1c68..315b0c2acf 100644 --- a/src/Hl7.Fhir.Serialization.R4.Tests/RoundtripSignature.cs +++ b/src/Hl7.Fhir.Serialization.R4.Tests/RoundtripSignature.cs @@ -69,16 +69,15 @@ public void WorksWithTypedElementSerializers() public void WorksWithPocoSerializers() { var sig = new Bundle() { Type = Bundle.BundleType.Document, Signature = new Signature() { Who = new ResourceReference("http://nu.nl") } }; - var json = new FhirJsonPocoSerializer().SerializeToString(sig); + var json = FhirJsonSerializer.Default.SerializeToString(sig); json.Should().Contain("\"who\""); var sig2 = new FhirJsonPocoDeserializer().DeserializeResource(json); sig.IsExactly(sig2).Should().BeTrue(); - var xml = new FhirXmlPocoSerializer().SerializeToString(sig); + var xml = FhirXmlSerializer.Default.SerializeToString(sig); xml.Should().Contain(""); var sig3 = new FhirXmlPocoDeserializer().DeserializeResource(xml); sig.IsExactly(sig3).Should().BeTrue(); - } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/DebugDump.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/DebugDump.cs index 0afd711690..6229913e56 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/DebugDump.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/DebugDump.cs @@ -41,8 +41,8 @@ public static void OutputJson(Base fragment) Console.WriteLine("(null)"); else { - Console.WriteLine(new FhirJsonSerializer(new SerializerSettings() { Pretty = true }).SerializeToString(fragment)); + Console.WriteLine(new FhirJsonSerializer().SerializeToString(fragment, pretty: true)); } } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatient.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatient.cs index 35db4ab3ad..2387fdb960 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatient.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatient.cs @@ -100,7 +100,7 @@ public static void CheckBundleEntryNavigation(ITypedElement node) Assert.IsNotNull(id); } - public static async Task RoundtripXml(Func navCreator) + public static void RoundtripXml(Func navCreator) { var tp = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); @@ -109,7 +109,6 @@ public static async Task RoundtripXml(Func navCreator) switch (nav) { case ISourceNode _: - break; case ITypedElement _: break; default: @@ -117,8 +116,8 @@ public static async Task RoundtripXml(Func navCreator) } string output; - if (nav is ISourceNode isn2) output = await isn2.ToXmlAsync(); - else if (nav is ITypedElement ien2) output = await ien2.ToXmlAsync(); + if (nav is ISourceNode isn2) output = isn2.ToXml(); + else if (nav is ITypedElement ien2) output = ien2.ToXml(); else throw Error.InvalidOperation("Fix unit test"); @@ -145,8 +144,8 @@ private static async Task compareJson(string filename, Func } string output; - if (nav is ISourceNode isn2) output = await isn2.ToJsonAsync(); - else if (nav is ITypedElement ien2) output = await ien2.ToJsonAsync(); + if (nav is ISourceNode isn2) output = isn2.ToJson(); + else if (nav is ITypedElement ien2) output = ien2.ToJson(); else throw Error.InvalidOperation("Fix unit test"); @@ -247,4 +246,4 @@ public static void CanReadThroughTypedElement(ITypedElement n, bool typed) } } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonTyped.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonTyped.cs index 33ee61c156..4f6fce10a3 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonTyped.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonTyped.cs @@ -81,26 +81,26 @@ public async Task PingpongJson() var tp = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); // will allow whitespace and comments to come through var navJson = await JsonParsingHelpers.ParseToTypedElementAsync(tp, new PocoStructureDefinitionSummaryProvider()); - var xml = await navJson.ToXmlAsync(); + var xml = navJson.ToXml(); var navXml = XmlParsingHelpers.ParseToTypedElement(xml, new PocoStructureDefinitionSummaryProvider()); - var json = await navXml.ToJsonAsync(); + var json = navXml.ToJson(); - List errors = new List(); + List errors = []; JsonAssert.AreSame(@"TestData\fp-test-patient.json", tp, json, errors); Console.WriteLine(String.Join("\r\n", errors)); Assert.AreEqual(0, errors.Count, "Errors were encountered comparing converted content"); } [TestMethod] - public async Task IgnoreElements() + public void WillIgnoreUnknownElements() { var patient = SourceNode.Resource("Patient", "Patient", SourceNode.Valued("id", "pat1")); - var jsonBare = await patient.ToTypedElement(new PocoStructureDefinitionSummaryProvider()).ToJsonAsync(new FhirJsonSerializationSettings { IgnoreUnknownElements = false }); + var jsonBare = patient.ToTypedElement(new PocoStructureDefinitionSummaryProvider()).ToJson(); Assert.IsTrue(jsonBare.Contains("pat1")); patient.Add(SourceNode.Valued("unknownElement", "someValue")); - var jsonUnknown = await patient.ToTypedElement(new PocoStructureDefinitionSummaryProvider(), settings: new TypedElementSettings { ErrorMode = TypedElementSettings.TypeErrorMode.Ignore }).ToJsonAsync(new FhirJsonSerializationSettings { IgnoreUnknownElements = true }); + var jsonUnknown = patient.ToTypedElement(new PocoStructureDefinitionSummaryProvider(), settings: new TypedElementSettings { ErrorMode = TypedElementSettings.TypeErrorMode.Ignore }).ToJson(); Assert.IsFalse(jsonUnknown.Contains("unknownElement")); } @@ -188,4 +188,4 @@ public async Task CatchParseErrors() } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonUntyped.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonUntyped.cs index cad016382a..6b842613b7 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonUntyped.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientJsonUntyped.cs @@ -69,7 +69,7 @@ public async Task TryInvalidUntypedSource() try { - var output = await xmlNav.ToJsonAsync(); + var output = xmlNav.ToJson(); Assert.Fail(); } catch (NotSupportedException) @@ -242,4 +242,4 @@ public async Task CatchParseErrors() } } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlTyped.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlTyped.cs index 68d917f133..9fe41e8470 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlTyped.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlTyped.cs @@ -97,9 +97,9 @@ public void CheckBundleEntryNavigation() [TestMethod] - public async Task RoundtripXml() + public void RoundtripXml() { - await ParseDemoPatient.RoundtripXml(reader => XmlParsingHelpers.ParseToTypedElement(reader, new PocoStructureDefinitionSummaryProvider())); + ParseDemoPatient.RoundtripXml(reader => XmlParsingHelpers.ParseToTypedElement(reader, new PocoStructureDefinitionSummaryProvider())); } [TestMethod] @@ -108,10 +108,10 @@ public async Task PingpongXml() var tp = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); // will allow whitespace and comments to come through var navXml = XmlParsingHelpers.ParseToTypedElement(tp, new PocoStructureDefinitionSummaryProvider()); - var json = await navXml.ToJsonAsync(); + var json = navXml.ToJson(); var navJson = await JsonParsingHelpers.ParseToTypedElementAsync(json, new PocoStructureDefinitionSummaryProvider()); - var xml = await navJson.ToXmlAsync(); + var xml = navJson.ToXml(); XmlAssert.AreSame("fp-test-patient.xml", tp, xml, ignoreSchemaLocation: true); } @@ -223,4 +223,4 @@ public void CatchParseErrors() } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlUntyped.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlUntyped.cs index 832bf0d418..bc9a18a6fc 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlUntyped.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientXmlUntyped.cs @@ -192,9 +192,9 @@ static void assertDiv(ISourceNode cnn) } [TestMethod] - public async Task RoundtripXmlUntyped() + public void RoundtripXmlUntyped() { - await ParseDemoPatient.RoundtripXml(xmlText => FhirXmlNode.Parse(xmlText)); + ParseDemoPatient.RoundtripXml(xmlText => FhirXmlNode.Parse(xmlText)); } [TestMethod] @@ -204,7 +204,7 @@ public async Task TryInvalidUntypedSource() try { - var output = await jsonNav.ToXmlAsync(); + var output = jsonNav.ToXml(); Assert.Fail(); } catch (NotSupportedException) diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJsonPoco.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJsonPoco.cs index 6c4a473c7a..45c576241b 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJsonPoco.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJsonPoco.cs @@ -14,7 +14,7 @@ public class SerializationExceptionHandlersJsonPoco private static T serializeResource(string json) where T : Resource { - var settings = new FhirJsonPocoDeserializerSettings() + var settings = new FhirJsonConverterOptions() { OnPrimitiveParseFailed = (ref Utf8JsonReader reader, Type targetType, diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs index 0b2c0e91bd..9d25c7802a 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs @@ -16,7 +16,7 @@ namespace Hl7.Fhir.Serialization.Tests [TestClass] public class SerializeDemoPatientJson { - public async Tasks.Task getJsonElement(string json, FhirJsonParsingSettings s = null) => + private static async Tasks.Task getJsonElement(string json, FhirJsonParsingSettings s = null) => await JsonParsingHelpers.ParseToTypedElementAsync(json, new PocoStructureDefinitionSummaryProvider(), settings: s); [TestMethod] @@ -25,9 +25,9 @@ public async Tasks.Task CanSerializeThroughNavigatorAndCompare() var json = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); var nav = await getJsonElement(json); - var output = await nav.ToJsonAsync(); + var output = nav.ToJson(); - List errors = new List(); + List errors = []; JsonAssert.AreSame(@"TestData\fp-test-patient.json", json, output, errors); Console.WriteLine(String.Join("\r\n", errors)); Assert.AreEqual(0, errors.Count, "Errors were encountered comparing converted content"); @@ -41,7 +41,7 @@ public async Tasks.Task TestPruneEmptyNodes() // Make sure permissive parsing is on - otherwise the parser will complain about all those empty nodes var nav = await getJsonElement(tp, new FhirJsonParsingSettings { PermissiveParsing = true }); - var output = await nav.ToJsonAsync(); + var output = nav.ToJson(); var doc = JObject.Parse(output); Assert.AreEqual(17, doc.DescendantsAndSelf().Count()); } @@ -54,7 +54,7 @@ public async Tasks.Task CanSerializeFromPoco() var pser = new FhirJsonParser(new ParserSettings { DisallowXsiAttributesOnRoot = false } ); var pat = await pser.ParseAsync(tp); - var output = await pat.ToJsonAsync(); + var output = pat.ToJson(); List errors = new List(); JsonAssert.AreSame(@"TestData\fp-test-patient.json", tp, output, errors); @@ -68,38 +68,16 @@ public async Tasks.Task DoesPretty() var json = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); var nav = await getJsonElement(json); - var output = await nav.ToJsonAsync(); - Assert.IsFalse(output.Substring(0, 20).Contains('\n')); - var pretty = await nav.ToJsonAsync(new FhirJsonSerializationSettings { Pretty = true }); - Assert.IsTrue(pretty.Substring(0, 20).Contains('\n')); - - var p = await new FhirJsonParser().ParseAsync(json); - output = await (new FhirJsonSerializer(new SerializerSettings { Pretty = false })).SerializeToStringAsync(p); - Assert.IsFalse(output.Substring(0, 20).Contains('\n')); - pretty = await (new FhirJsonSerializer(new SerializerSettings { Pretty = true, AppendNewLine = true })).SerializeToStringAsync(p); - Assert.IsTrue(pretty.Substring(0, 20).Contains('\n')); - } - - [TestMethod] - public async Tasks.Task TestAppendNewLine() - { - var json = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); - - var nav = await getJsonElement(json); - var output = await nav.ToJsonAsync(); - Assert.IsFalse(output.Contains('\n')); - var pretty = await nav.ToJsonAsync(new FhirJsonSerializationSettings { Pretty = true }); - Assert.IsTrue(pretty.Contains('\n')); - var lastLine = pretty.Split('\n').Last(); - Assert.IsFalse(string.IsNullOrEmpty(lastLine)); + var output = nav.ToJson(); + Assert.IsFalse(output[..20].Contains('\n')); + var pretty = nav.ToJson(pretty: true); + Assert.IsTrue(pretty[..20].Contains('\n')); var p = await new FhirJsonParser().ParseAsync(json); - output = await (new FhirJsonSerializer(new SerializerSettings { Pretty = false, AppendNewLine = true })).SerializeToStringAsync(p); - lastLine = output.Split('\n').Last(); - Assert.IsTrue(string.IsNullOrEmpty(lastLine)); - pretty = await (new FhirJsonSerializer(new SerializerSettings { Pretty = true, AppendNewLine = true })).SerializeToStringAsync(p); - lastLine = pretty.Split('\n').Last(); - Assert.IsTrue(string.IsNullOrEmpty(lastLine)); + output = new FhirJsonSerializer().SerializeToString(p, pretty: false); + Assert.IsFalse(output[..20].Contains('\n')); + pretty = new FhirJsonSerializer().SerializeToString(p, pretty: true); + Assert.IsTrue(pretty[..20].Contains('\n')); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs index 523a5caf7f..177af85849 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs @@ -8,131 +8,108 @@ using System.Linq; using Tasks = System.Threading.Tasks; -namespace Hl7.Fhir.Serialization.Tests +namespace Hl7.Fhir.Serialization.Tests; + +[TestClass] +public class SerializeDemoPatientXml { - [TestClass] - public class SerializeDemoPatientXml + public ITypedElement getXmlElement(string xml, FhirXmlParsingSettings s = null) => + XmlParsingHelpers.ParseToTypedElement(xml, new PocoStructureDefinitionSummaryProvider(), s); + public async Tasks.Task getJsonElement(string json, FhirJsonParsingSettings s = null) => + await JsonParsingHelpers.ParseToTypedElementAsync(json, new PocoStructureDefinitionSummaryProvider(), settings: s); + + + [TestMethod] + public void CanSerializeThroughNavigatorAndCompare() + { + var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); + var nav = getXmlElement(tpXml); + var output = nav.ToXml(); + XmlAssert.AreSame("fp-test-patient.xml", tpXml, output, ignoreSchemaLocation: true); + } + + [TestMethod] + public void TestPruneEmptyNodes() + { + var tpXml = File.ReadAllText(Path.Combine("TestData", "test-empty-nodes.xml")); + + // Make sure permissive parsing is on - otherwise the parser will complain about all those empty nodes + var nav = getXmlElement(tpXml, new FhirXmlParsingSettings { PermissiveParsing = true }); + var doc = nav.ToXDocument().Root!; + Assert.AreEqual(10, doc.DescendantNodesAndSelf().Count()); // only 8 nodes + 2 comments left after pruning + } + + [TestMethod] + public void TestElementReordering() + { + var tpXml = File.ReadAllText(Path.Combine("TestData", "patient-out-of-order.xml")); + var nav = getXmlElement(tpXml, new FhirXmlParsingSettings { PermissiveParsing = true }); // since the order is incorrect + var root = nav.ToXDocument().Root!; + + var orderedNames = root.Elements().Select(e => e.Name.LocalName).ToList(); + CollectionAssert.AreEqual(new[] { "id", "text", "identifier", "identifier", "active", "name", "telecom" }, orderedNames); + + var orderedNameNames = root.Element("{http://hl7.org/fhir}name") + .Elements().Select(e => e.Name.LocalName).ToList(); + CollectionAssert.AreEqual(new[] { "use", "family", "given" }, orderedNameNames); + } + + [TestMethod] + public async Tasks.Task CanSerializeFromPoco() + { + var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); + var pser = new FhirXmlParser(new ParserSettings { DisallowXsiAttributesOnRoot = false }); + var pat = await pser.ParseAsync(tpXml); + + var nav = pat.ToTypedElement(); + var output = nav.ToXml(); + XmlAssert.AreSame("fp-test-patient.xml", tpXml, output, ignoreSchemaLocation: true); + } + + [TestMethod] + public async Tasks.Task CompareSubtrees() + { + var tpXml = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.xml")); + var tpJson = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); + // If on a Unix platform replace \\r\\n in json strings to \\n. + if(Environment.NewLine == "\n") + tpJson = tpJson.Replace(@"\r\n", @"\n"); + var pat = await (new FhirXmlParser()).ParseAsync(tpXml); + + var navXml = getXmlElement(tpXml); + var navJson = await getJsonElement(tpJson); + var navPoco = pat.ToTypedElement(); + assertAreAllEqual(navXml, navJson, navPoco); + + // A subtree that's a normal datatype + var subnavXml = navXml.Children("photo").First(); + var subnavJson = navJson.Children("photo").First(); + var subnavPoco = navPoco.Children("photo").First(); + assertAreAllEqual(subnavXml, subnavJson, subnavPoco); + } + + private void assertAreAllEqual(ITypedElement subnavXml, ITypedElement subnavJson, ITypedElement subnavPoco) + { + Assert.IsTrue(subnavXml.IsEqualTo(subnavJson).Success); + Assert.IsTrue(subnavJson.IsEqualTo(subnavPoco).Success); + Assert.IsTrue(subnavPoco.IsEqualTo(subnavXml).Success); + } + + [TestMethod] + public async Tasks.Task DoesPretty() { - public ITypedElement getXmlElement(string xml, FhirXmlParsingSettings s = null) => - XmlParsingHelpers.ParseToTypedElement(xml, new PocoStructureDefinitionSummaryProvider(), s); - public async Tasks.Task getJsonElement(string json, FhirJsonParsingSettings s = null) => - await JsonParsingHelpers.ParseToTypedElementAsync(json, new PocoStructureDefinitionSummaryProvider(), settings: s); - - - [TestMethod] - public async Tasks.Task CanSerializeThroughNavigatorAndCompare() - { - var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var nav = getXmlElement(tpXml); - var output = await nav.ToXmlAsync(); - XmlAssert.AreSame("fp-test-patient.xml", tpXml, output, ignoreSchemaLocation: true); - } - - [TestMethod] - public void TestPruneEmptyNodes() - { - var tpXml = File.ReadAllText(Path.Combine("TestData", "test-empty-nodes.xml")); - - // Make sure permissive parsing is on - otherwise the parser will complain about all those empty nodes - var nav = getXmlElement(tpXml, new FhirXmlParsingSettings { PermissiveParsing = true }); - var doc = nav.ToXDocument().Root; - Assert.AreEqual(10, doc.DescendantNodesAndSelf().Count()); // only 8 nodes + 2 comments left after pruning - } - - [TestMethod] - public void TestElementReordering() - { - var tpXml = File.ReadAllText(Path.Combine("TestData", "patient-out-of-order.xml")); - var nav = getXmlElement(tpXml, new FhirXmlParsingSettings { PermissiveParsing = true }); // since the order is incorrect - var root = nav.ToXDocument().Root; - - var orderedNames = root.Elements().Select(e => e.Name.LocalName).ToList(); - CollectionAssert.AreEqual(new[] { "id", "text", "identifier", "identifier", "active", "name", "telecom" }, orderedNames); - - var orderedNameNames = root.Element("{http://hl7.org/fhir}name") - .Elements().Select(e => e.Name.LocalName).ToList(); - CollectionAssert.AreEqual(new[] { "use", "family", "given" }, orderedNameNames); - } - - [TestMethod] - public async Tasks.Task CanSerializeFromPoco() - { - var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var pser = new FhirXmlParser(new ParserSettings { DisallowXsiAttributesOnRoot = false }); - var pat = await pser.ParseAsync(tpXml); - - var nav = pat.ToTypedElement(); - var output = await nav.ToXmlAsync(); - XmlAssert.AreSame("fp-test-patient.xml", tpXml, output, ignoreSchemaLocation: true); - } - - [TestMethod] - public async Tasks.Task CompareSubtrees() - { - var tpXml = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.xml")); - var tpJson = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); - // If on a Unix platform replace \\r\\n in json strings to \\n. - if(Environment.NewLine == "\n") - tpJson = tpJson.Replace(@"\r\n", @"\n"); - var pat = await (new FhirXmlParser()).ParseAsync(tpXml); - - var navXml = getXmlElement(tpXml); - var navJson = await getJsonElement(tpJson); - var navPoco = pat.ToTypedElement(); - assertAreAllEqual(navXml, navJson, navPoco); - - // A subtree that's a normal datatype - var subnavXml = navXml.Children("photo").First(); - var subnavJson = navJson.Children("photo").First(); - var subnavPoco = navPoco.Children("photo").First(); - assertAreAllEqual(subnavXml, subnavJson, subnavPoco); - } - - private void assertAreAllEqual(ITypedElement subnavXml, ITypedElement subnavJson, ITypedElement subnavPoco) - { - Assert.IsTrue(subnavXml.IsEqualTo(subnavJson).Success); - Assert.IsTrue(subnavJson.IsEqualTo(subnavPoco).Success); - Assert.IsTrue(subnavPoco.IsEqualTo(subnavXml).Success); - } - - [TestMethod] - public async Tasks.Task DoesPretty() - { - var xml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - - var nav = getXmlElement(xml); - var output = await nav.ToXmlAsync(); - Assert.IsFalse(output.Substring(0, 50).Contains('\n')); - var pretty = await nav.ToXmlAsync(new FhirXmlSerializationSettings { Pretty = true }); - Assert.IsTrue(pretty.Substring(0, 50).Contains('\n')); - - var p = await (new FhirXmlParser()).ParseAsync(xml); - output = await (new FhirXmlSerializer(new SerializerSettings { Pretty = false })).SerializeToStringAsync(p); - Assert.IsFalse(output.Substring(0, 50).Contains('\n')); - pretty = await (new FhirXmlSerializer(new SerializerSettings { Pretty = true })).SerializeToStringAsync(p); - Assert.IsTrue(pretty.Substring(0, 50).Contains('\n')); - } - - [TestMethod] - public async Tasks.Task TestAppendNewLine() - { - var xml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - - var nav = getXmlElement(xml); - var output = await nav.ToXmlAsync(); - Assert.IsFalse(output.Substring(0, 50).Contains('\n')); - var pretty = await nav.ToXmlAsync(new FhirXmlSerializationSettings { Pretty = true }); - Assert.IsTrue(pretty.Substring(0, 50).Contains('\n')); - var lastLine = pretty.Split('\n').Last(); - Assert.IsFalse(string.IsNullOrEmpty(lastLine)); - - var p = await (new FhirXmlParser()).ParseAsync(xml); - output = await (new FhirXmlSerializer(new SerializerSettings { Pretty = false, AppendNewLine = true })).SerializeToStringAsync(p); - lastLine = output.Split('\n').Last(); - Assert.IsTrue(string.IsNullOrEmpty(lastLine)); - pretty = await (new FhirXmlSerializer(new SerializerSettings { Pretty = true, AppendNewLine = true })).SerializeToStringAsync(p); - lastLine = pretty.Split('\n').Last(); - Assert.IsTrue(string.IsNullOrEmpty(lastLine)); - } + var xml = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.xml")); + + var nav = getXmlElement(xml); + var output = nav.ToXml(); + Assert.IsFalse(output[..50].Contains('\n')); + var pretty = nav.ToXml(pretty: true); + Assert.IsTrue(pretty[..50].Contains('\n')); + + var p = await new FhirXmlParser().ParseAsync(xml); + output = new FhirXmlSerializer().SerializeToString(p, pretty: false); + Assert.IsFalse(output[..50].Contains('\n')); + pretty = new FhirXmlSerializer().SerializeToString(p, pretty: true); + Assert.IsTrue(pretty[..50].Contains('\n')); } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs index bc7126e87c..1c4900f6ca 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs @@ -32,15 +32,12 @@ public void DeterminePocoInstanceTypeWithRedirect() } [TestMethod] - [Ignore] [TemporarilyChanged] // We once again cannot do this, as base no longer knows if it has a parent public async Tasks.Task CanSerializeSubtree() { - var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var tpJson = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.json")); - // If on a Unix platform replace \\r\\n in json strings to \\n. - if(Environment.NewLine == "\n") - tpJson = tpJson.Replace(@"\r\n", @"\n"); + var tpXml = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.xml")); + var tpJson = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); + var pat = await (new FhirXmlParser()).ParseAsync(tpXml); // Should work on the parent resource @@ -72,10 +69,10 @@ private async Tasks.Task testSubtree(ITypedElement navXml, ITypedElement navJson { assertAreNavsEqual(navXml, navJson, navPoco); - var navRtXml = await JsonParsingHelpers.ParseToTypedElement(await navXml.ToJsonAsync(), navXml.InstanceType, + var navRtXml = await JsonParsingHelpers.ParseToTypedElement(navXml.ToJson(), navXml.InstanceType, new PocoStructureDefinitionSummaryProvider(), navXml.Name); var navRtJson = navJson.ToPoco().ToTypedElement(navJson.Name); - var navRtPoco = XmlParsingHelpers.ParseToTypedElement(await navPoco.ToXmlAsync(), navPoco.InstanceType, + var navRtPoco = XmlParsingHelpers.ParseToTypedElement(navPoco.ToXml(), navPoco.InstanceType, new PocoStructureDefinitionSummaryProvider()); assertAreNavsEqual(navRtXml, navRtJson, navRtPoco); } diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SummaryTests.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SummaryTests.cs index 4e7445a691..dda60e695a 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SummaryTests.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SummaryTests.cs @@ -17,7 +17,7 @@ public ITypedElement getXmlNode(string xml, FhirXmlParsingSettings s = null) => XmlParsingHelpers.ParseToTypedElement(xml, new PocoStructureDefinitionSummaryProvider(), s); [TestMethod] - public async Task Summary() + public void Summary() { var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); var typeinfo = new PocoStructureDefinitionSummaryProvider().Provide("Patient"); @@ -25,7 +25,7 @@ public async Task Summary() var nav = new ScopedNode(getXmlNode(tpXml)); var masker = MaskingNode.ForSummary(nav); - var output = await masker.ToXmlAsync(); + var output = masker.ToXml(); var maskedChildren = masker.Children().ToList(); Assert.IsTrue(maskedChildren.Count < inSummary.Count); @@ -33,14 +33,14 @@ public async Task Summary() } [TestMethod] - public async Task SummaryText() + public void SummaryText() { var tpXml = File.ReadAllText(Path.Combine("TestData", "mask-text.xml")); var typeinfo = new PocoStructureDefinitionSummaryProvider().Provide("ValueSet"); var nav = new ScopedNode(getXmlNode(tpXml)); var masker = MaskingNode.ForText(nav); - var output = await masker.ToXmlAsync(); + var output = masker.ToXml(); var m = masker.Descendants().ToList(); var maskedChildren = masker.Descendants().Count(); @@ -48,14 +48,14 @@ public async Task SummaryText() } [TestMethod] - public async Task SummaryData() + public void SummaryData() { var tpXml = File.ReadAllText(Path.Combine("TestData", "mask-text.xml")); var typeinfo = new PocoStructureDefinitionSummaryProvider().Provide("ValueSet"); var nav = new ScopedNode(getXmlNode(tpXml)); var masker = MaskingNode.ForData(nav); - var output = await masker.ToXmlAsync(); + var output = masker.ToXml(); var maskedChildren = masker.Descendants().Count(); Assert.AreEqual(nav.Descendants().Count() - 3, maskedChildren); diff --git a/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs b/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs index 649f1ee220..d47e5d11fd 100644 --- a/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs +++ b/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs @@ -28,7 +28,7 @@ public async Task CheckCopyAllFields() var p = await new FhirXmlParser().ParseAsync(xml); var p2 = (Patient)p.DeepCopy(); - var xml2 = await new FhirXmlSerializer().SerializeToStringAsync(p2); + var xml2 = new FhirXmlSerializer().SerializeToString(p2); XmlAssert.AreSame("TestPatient.xml", xml, xml2); } @@ -64,4 +64,4 @@ public void CheckCopyPerformance() } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs index 8b995a6074..1656d7e39d 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs @@ -161,7 +161,7 @@ public async Tasks.Task RetainSpacesInAttribute() Assert.IsTrue(basic.GetStringExtension("http://blabla.nl").Contains("\n")); - var outp = await FhirXmlSerializer.SerializeToStringAsync(basic); + var outp = FhirXmlSerializer.SerializeToString(basic); Assert.IsTrue(outp.Contains(" ")); } @@ -259,13 +259,13 @@ public async Tasks.Task EdgecaseRoundtrip() var poco = await FhirJsonParser.ParseAsync(json); Assert.IsNotNull(poco); - var xml = await FhirXmlSerializer.SerializeToStringAsync(poco); + var xml = FhirXmlSerializer.SerializeToString(poco); Assert.IsNotNull(xml); await File.WriteAllTextAsync(Path.Combine(tempPath, "edgecase.xml"), xml); poco = await FhirXmlParser.ParseAsync(xml); Assert.IsNotNull(poco); - var json2 = await FhirJsonSerializer.SerializeToStringAsync(poco); + var json2 = FhirJsonSerializer.SerializeToString(poco); Assert.IsNotNull(json2); await File.WriteAllTextAsync(Path.Combine(tempPath, "edgecase.json"), json2); @@ -283,12 +283,12 @@ public async Tasks.Task ContainedBaseIsNotAddedToId() o.Contained.Add(p); o.ResourceBase = new Uri("http://nu.nl/fhir"); - var xml = await FhirXmlSerializer.SerializeToStringAsync(o); + var xml = FhirXmlSerializer.SerializeToString(o); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); var o2 = await FhirXmlParser.ParseAsync(xml); o2.ResourceBase = new Uri("http://nu.nl/fhir"); - xml = await FhirXmlSerializer.SerializeToStringAsync(o2); + xml = FhirXmlSerializer.SerializeToString(o2); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); } @@ -307,7 +307,7 @@ public async Tasks.Task EmptyRoundTrip() } }; - var json = await FhirJsonSerializer.SerializeToStringAsync(patient); + var json = FhirJsonSerializer.SerializeToString(patient); var parsedPatient = await FhirJsonParser.ParseAsync(json); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); @@ -324,7 +324,7 @@ public async Tasks.Task EmptyRoundTrip() } } - var xml = await FhirXmlSerializer.SerializeToStringAsync(patient); + var xml = FhirXmlSerializer.SerializeToString(patient); parsedPatient = await FhirXmlParser.ParseAsync(xml); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); @@ -351,9 +351,9 @@ public async Tasks.Task SerializeNarrativeWithQuotes() Text = new Narrative() { Div = "
Nasty, a text with both \"double\" quotes and 'single' quotes
" } }; - var xml = await FhirXmlSerializer.SerializeToStringAsync(p); + var xml = FhirXmlSerializer.SerializeToString(p); Assert.IsNotNull(await FhirXmlParser.ParseAsync(xml)); - var json = await FhirJsonSerializer.SerializeToStringAsync(p); + var json = FhirJsonSerializer.SerializeToString(p); Assert.IsNotNull(await FhirJsonParser.ParseAsync(json)); } @@ -383,4 +383,4 @@ public void ParseEmptyContained() ExceptionAssert.Throws(() => parser.Parse(xml)); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs index 8ff0927d08..45e2500c8b 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs @@ -29,25 +29,25 @@ public partial class SerializationTests private readonly Meta metaPoco = new Meta { LastUpdated = new DateTimeOffset(2014, 12, 24, 16, 30, 56, 31, new TimeSpan(1, 0, 0)), VersionId = "3141" }; [TestMethod] - public async Tasks.Task SerializeMetaXml() + public void SerializeMetaXml() { - var xml = await new FhirXmlSerializer().SerializeToStringAsync(metaPoco, root: "meta"); + var xml = new FhirXmlSerializer().SerializeToString(metaPoco, rootName: "meta"); Assert.AreEqual(metaXml, xml); } [TestMethod] - public async Tasks.Task SerializeMetaJson() + public void SerializeMetaJson() { - var json = await new FhirJsonSerializer().SerializeToStringAsync(metaPoco); - Assert.AreEqual(metaJson, json); + var json = new FhirJsonSerializer().SerializeToString(metaPoco); + JsonAssert.AreSame("0", metaJson, json); } [TestMethod] public async Tasks.Task ParseMetaXml() { var poco = (Meta)(await new FhirXmlParser().ParseAsync(metaXml, typeof(Meta))); - var xml = await new FhirXmlSerializer().SerializeToStringAsync(poco, root: "meta"); + var xml = new FhirXmlSerializer().SerializeToString(poco, rootName: "meta"); Assert.IsTrue(poco.IsExactly(metaPoco)); Assert.AreEqual(metaXml, xml); @@ -73,10 +73,10 @@ public void ParsePatientXmlNullType() public async Tasks.Task ParseMetaJson() { var poco = (Meta)await (new FhirJsonParser().ParseAsync(metaJson, typeof(Meta))); - var json = await FhirJsonSerializer.SerializeToStringAsync(poco); + var json = FhirJsonSerializer.SerializeToString(poco); Assert.IsTrue(poco.IsExactly(metaPoco)); - Assert.AreEqual(metaJson, json); + JsonAssert.AreSame("0", metaJson, json); } [TestMethod] @@ -93,22 +93,22 @@ public async Tasks.Task ParsePatientJsonNullType() } [TestMethod] - public async Tasks.Task AvoidBOMUse() + public void AvoidBOMUse() { Bundle b = new Bundle() { Total = 1000 }; - var data = await FhirJsonSerializer.SerializeToBytesAsync(b); + var data = FhirJsonSerializer.SerializeToBytes(b); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); - data = await FhirXmlSerializer.SerializeToBytesAsync(b); + data = FhirXmlSerializer.SerializeToBytes(b); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); - Patient p = new Patient() { Active = true }; + Patient p = new() { Active = true }; - data = await FhirJsonSerializer.SerializeToBytesAsync(p); + data = FhirJsonSerializer.SerializeToBytes(p); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); - data = await FhirXmlSerializer.SerializeToBytesAsync(p); + data = FhirXmlSerializer.SerializeToBytes(p); Assert.IsFalse(data[0] == Encoding.UTF8.GetPreamble()[0]); } @@ -131,56 +131,15 @@ public void TestProbing() private readonly FhirXmlParser FhirXmlParser = new FhirXmlParser(); private readonly FhirJsonParser FhirJsonParser = new FhirJsonParser(); - - //[TestMethod] - //public void HandleCommentsJson() - //{ - // string json = TestDataHelper.ReadTestData("TestPatient.json"); - - // var pat = FhirJsonParser.Parse(json); - - // Assert.AreEqual(1, pat.Telecom[0].FhirCommentsElement.Count); - // Assert.AreEqual(" home communication details aren't known ", pat.Telecom[0].FhirComments.First()); - - // pat.Telecom[0].FhirCommentsElement.Add(new FhirString("A second line")); - - // json = FhirJsonSerializer.SerializeToString(pat); - // pat = FhirJsonParser.Parse(json); - - // Assert.AreEqual(2, pat.Telecom[0].FhirCommentsElement.Count); - // Assert.AreEqual(" home communication details aren't known ", pat.Telecom[0].FhirComments.First()); - // Assert.AreEqual("A second line", pat.Telecom[0].FhirComments.Skip(1).First()); - //} - - //[TestMethod, Ignore] - //public void HandleCommentsXml() - //{ - // string xml = TestDataHelper.ReadTestData("TestPatient.xml"); - - // var pat = FhirXmlParser.Parse(xml); - - // Assert.AreEqual(1, pat.Name[0].FhirCommentsElement.Count); - // Assert.AreEqual("See if this is roundtripped", pat.Name[0].FhirComments.First()); - - // pat.Name[0].FhirCommentsElement.Add(new FhirString("A second line")); - - // xml = FhirXmlSerializer.SerializeToString(pat); - - // Assert.AreEqual(2, pat.Name[0].FhirCommentsElement.Count); - // Assert.AreEqual("See if this is roundtripped", pat.Name[0].FhirComments.First()); - // Assert.AreEqual("A second line", pat.Name[0].FhirComments.Skip(1).First()); - //} - - [TestMethod] - public async Tasks.Task BundleLinksUnaltered() + public void BundleLinksUnaltered() { var b = new Bundle { NextLink = new Uri("Organization/123456/_history/123456", UriKind.Relative) }; - var xml = await new FhirXmlSerializer().SerializeToStringAsync(b); + var xml = new FhirXmlSerializer().SerializeToString(b); b = FhirXmlParser.Parse(xml); @@ -188,7 +147,7 @@ public async Tasks.Task BundleLinksUnaltered() } [TestMethod] - public async Tasks.Task TestDecimalPrecisionSerializationInJson() + public void TestDecimalPrecisionSerializationInJson() { var dec6 = 6m; var dec60 = 6.0m; @@ -196,8 +155,8 @@ public async Tasks.Task TestDecimalPrecisionSerializationInJson() var obs = new Observation(); obs.AddExtension("http://example.org/DecimalPrecision", ext); - var json = await FhirJsonSerializer.SerializeToStringAsync(obs); - var obs2 = await FhirJsonParser.ParseAsync(json); + var json = FhirJsonSerializer.SerializeToString(obs); + var obs2 = FhirJsonParser.Parse(json); Assert.AreEqual("6", ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); @@ -205,8 +164,8 @@ public async Tasks.Task TestDecimalPrecisionSerializationInJson() obs = new Observation(); obs.AddExtension("http://example.org/DecimalPrecision", ext); - json = await FhirJsonSerializer.SerializeToStringAsync(obs); - obs2 = await FhirJsonParser.ParseAsync(json); + json = FhirJsonSerializer.SerializeToString(obs); + obs2 = FhirJsonParser.Parse(json); Assert.AreEqual("6.0", ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); } @@ -219,7 +178,7 @@ public async Tasks.Task TestLongDecimalSerialization() var obs = new Observation(); obs.AddExtension("http://example.org/DecimalPrecision", ext); - var json = await FhirJsonSerializer.SerializeToStringAsync(obs); + var json = FhirJsonSerializer.SerializeToString(obs); var obs2 = await FhirJsonParser.ParseAsync(json); Assert.AreEqual(dec.ToString(CultureInfo.InvariantCulture), ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); @@ -231,7 +190,7 @@ public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() var dec6 = 6m; var ext = new FhirDecimal(dec6); var obs = new Observation { Value = new FhirDecimal(dec6) }; - var json = await FhirJsonSerializer.SerializeToStringAsync(obs); + var json = FhirJsonSerializer.SerializeToString(obs); try { var obs2 = await FhirJsonParser.ParseAsync(json); @@ -244,7 +203,7 @@ public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() } [TestMethod] - public async Tasks.Task TestSerializeWhitespacesInXml() + public void TestSerializeWhitespacesInXml() { var patient = new Patient { @@ -254,26 +213,20 @@ public async Tasks.Task TestSerializeWhitespacesInXml() } }; - var trimmed = await FhirXmlSerializer.SerializeToStringAsync(patient); + var trimmed = FhirXmlSerializer.SerializeToString(patient); Assert.IsFalse(trimmed.Contains(" Smith")); Assert.IsFalse(trimmed.Contains("Smith ")); Assert.IsTrue(trimmed.Contains("\"Smith\"")); - - var notTrimmed = await new FhirXmlSerializer(new SerializerSettings { TrimWhiteSpacesInXml = false }).SerializeToStringAsync(patient); - Assert.IsTrue(notTrimmed.Contains(" Smith")); - Assert.IsTrue(notTrimmed.Contains("Smith ")); - Assert.IsFalse(notTrimmed.Contains("\"Smith\"")); - } [TestMethod] - public async Tasks.Task TryScriptInject() + public void TryScriptInject() { var x = new Patient(); x.Name.Add(HumanName.ForFamily("")); - var xml = await FhirXmlSerializer.SerializeToStringAsync(x); + var xml = FhirXmlSerializer.SerializeToString(x); Assert.IsFalse(xml.Contains("(xml); - string outp = await FhirXmlSerializer.SerializeToStringAsync(p); + string outp = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(outp.Contains("\"male\"")); // Pollute the data with an incorrect administrative gender p.GenderElement.ObjectValue = "superman"; - outp = await FhirXmlSerializer.SerializeToStringAsync(p); + outp = FhirXmlSerializer.SerializeToString(p); Assert.IsFalse(outp.Contains("\"male\"")); Assert.IsTrue(outp.Contains("\"superman\"")); } [TestMethod] - public async Tasks.Task TestNullExtensionRemoval() + public void TestNullExtensionRemoval() { var p = new Patient { @@ -336,60 +289,33 @@ public async Tasks.Task TestNullExtensionRemoval() null }, - Contact = new List - { + Contact = + [ null, - new Patient.ContactComponent { Name = HumanName.ForFamily("Kramer") }, - } + new Patient.ContactComponent { Name = HumanName.ForFamily("Kramer") } + ] }; - var xml = await FhirXmlSerializer.SerializeToStringAsync(p); + var xml = FhirXmlSerializer.SerializeToString(p); - var p2 = await (new FhirXmlParser()).ParseAsync(xml); + var p2 = (new FhirXmlParser()).Parse(xml); Assert.AreEqual(1, p2.Extension.Count); Assert.AreEqual(1, p2.Contact.Count); } - //An empty object is not allowed - //[TestMethod] - //public void SerializeEmptyParams() - //{ - // var par = new Parameters(); - // var xml = FhirXmlSerializer.SerializeToString(par); - - // var par2 = (new FhirXmlParser()).Parse(xml); - // Assert.AreEqual(0, par2.Parameter.Count); - //} - [TestMethod] public async Tasks.Task SerializeJsonWithPlainDiv() { - // var res = new ValueSet() { Url = "http://example.org/fhir/ValueSet/MyValueSetExample" }; - string json = TestDataHelper.ReadTestData(@"TestPatient.json"); Assert.IsNotNull(json); var parser = new FhirJsonParser { Settings = { PermissiveParsing = true } }; var pat = await parser.ParseAsync(json); Assert.IsNotNull(pat); - var xml = await FhirXmlSerializer.SerializeToStringAsync(pat); + var xml = FhirXmlSerializer.SerializeToString(pat); Assert.IsNotNull(xml); } - [TestMethod] - public async Tasks.Task TestClaimJsonSerialization() - { - var c = new Claim(); - c.Payee = new Claim.PayeeComponent(); - c.Payee.Type = new CodeableConcept(null, "test"); - c.Payee.Party = new ResourceReference("Practitioner/example", "Example, Dr John"); - - string json = await FhirJsonSerializer.SerializeToStringAsync(c); - var c2 = await new FhirJsonParser().ParseAsync(json); - Assert.AreEqual("test", c2.Payee.Type.Coding[0].Code); - Assert.AreEqual("Practitioner/example", c2.Payee.Party.Reference); - } - [FhirType("Bundle")] //[DataContract] public class CustomBundle : Bundle @@ -400,7 +326,7 @@ public CustomBundle() : base() { } // [WMR 20170825] Richard Kavanagh: runtime exception while serializating derived PoCo classes // Workaround: add the FhirType attribute to derived class [TestMethod] - public async Tasks.Task TestDerivedPoCoSerialization() + public void TestDerivedPoCoSerialization() { ModelInfo.ModelInspector.ImportType(typeof(CustomBundle)); @@ -410,10 +336,10 @@ public async Tasks.Task TestDerivedPoCoSerialization() Id = "MyBundle" }; - var xml = await FhirXmlSerializer.SerializeToStringAsync(bundle); + var xml = FhirXmlSerializer.SerializeToString(bundle); Assert.IsNotNull(xml); - var json = await FhirJsonSerializer.SerializeToStringAsync(bundle); + var json = FhirJsonSerializer.SerializeToString(bundle); Assert.IsNotNull(json); } @@ -436,7 +362,9 @@ public void TestSerializeToXmlDocument() Gender = AdministrativeGender.Male }; +#pragma warning disable CS0618 // Type or member is obsolete var doc = FhirXmlSerializer.SerializeToDocument(patientOne); +#pragma warning restore CS0618 // Type or member is obsolete Assert.IsNotNull(doc); var root = doc.Root; @@ -448,7 +376,7 @@ public void TestSerializeToXmlDocument() // [WMR 20180409] NEW: Serialize to JObject [TestMethod] - public async Tasks.Task TestSerializeToJsonDocument() + public void TestSerializeToJsonDocument() { // Note: output order is defined by core resource/datatype definitions! @@ -464,13 +392,13 @@ public async Tasks.Task TestSerializeToJsonDocument() }; var serializer = new FhirJsonSerializer(); - var jsonText = await serializer.SerializeToStringAsync(patientOne); + var jsonText = serializer.SerializeToString(patientOne); Assert.IsNotNull(jsonText); var doc = JObject.Parse(jsonText); Assert.AreEqual(8, doc.Count); // Including resourceType - JToken assertProperty(JToken t, string expectedName) + static JToken assertProperty(JToken t, string expectedName) { Assert.AreEqual(JTokenType.Property, t.Type); var p = t as JProperty; @@ -479,7 +407,7 @@ JToken assertProperty(JToken t, string expectedName) return t; } - JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expectedType, object expectedValue) + static JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expectedType, object expectedValue) { var p = t as JProperty; Assert.IsNotNull(p); @@ -491,7 +419,8 @@ JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expecte return t; } - JToken assertStringProperty(JToken t, string expectedName, object expectedValue) => assertPrimitiveProperty(t, expectedName, JTokenType.String, expectedValue); + static JToken assertStringProperty(JToken t, string expectedName, object expectedValue) => + assertPrimitiveProperty(t, expectedName, JTokenType.String, expectedValue); Assert.AreEqual(JTokenType.Property, doc.First.Type); var token = assertStringProperty(doc.First, "resourceType", "Patient"); @@ -527,18 +456,18 @@ JToken assertPrimitiveProperty(JToken t, string expectedName, JTokenType expecte /// This test proves issue 583: https://github.com/FirelyTeam/firely-net-sdk/issues/583 /// [TestMethod] - public async Tasks.Task SummarizeSerializingTest() + public void SummarizeSerializingTest() { var patient = new Patient(); var telecom = new ContactPoint(ContactPoint.ContactPointSystem.Phone, ContactPoint.ContactPointUse.Work, "0471 144 099"); telecom.AddExtension("http://healthconnex.com.au/hcxd/Phone/IsMain", new FhirBoolean(true)); patient.Telecom.Add(telecom); - var doc = await FhirXmlSerializer.SerializeToStringAsync(patient, Fhir.Rest.SummaryType.True); + var doc = FhirXmlSerializer.SerializeToString(patient, Fhir.Rest.SummaryType.True); Assert.IsFalse(doc.Contains("(json); Assert.IsTrue(patient.IsExactly(res), "1"); @@ -560,8 +489,8 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() Assert.IsTrue(patient.IsExactly(res), "2"); // Is the serialization still correct without milliseconds? - var json2 = await new FhirJsonSerializer().SerializeToStringAsync(patient); - Assert.AreEqual(json, json2, "3"); + var json2 = new FhirJsonSerializer().SerializeToString(patient); + JsonAssert.AreSame("3", json, json2); // Is the parsing still correct with a few milliseconds and TimeZone? patient = new Patient { Meta = new Meta { LastUpdated = new DateTimeOffset(2018, 8, 13, 13, 41, 56, 12, TimeSpan.Zero) } }; @@ -570,8 +499,8 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() Assert.IsTrue(patient.IsExactly(res), "4"); // Is the serialization still correct with a few milliseconds? - json2 = await new FhirJsonSerializer().SerializeToStringAsync(patient); - Assert.AreEqual(json, json2, "5"); + json2 = new FhirJsonSerializer().SerializeToString(patient); + JsonAssert.AreSame("5", json, json2); } [TestMethod] @@ -586,7 +515,7 @@ public async Tasks.Task SerializerHandlesEmptyChildObjects() poco.Meta = new Meta(); - var reserialized = await poco.ToJsonAsync(); + var reserialized = poco.ToJson(); var newPoco = await fhirJsonParser.ParseAsync(reserialized); @@ -603,14 +532,18 @@ public void IncludeMandatoryInElementsSummaryTest() }; // default behavior - var json = new FhirJsonSerializer().SerializeToDocument(obs, elements: new[] { "issued" }); - +#pragma warning disable CS0618 // Type or member is obsolete + var json = new FhirJsonSerializer().SerializeToDocument(obs, elements: ["issued"]); +#pragma warning restore CS0618 // Type or member is obsolete Assert.IsTrue(json.ContainsKey("issued")); + Assert.IsFalse(json.ContainsKey("status")); // Adding mandatory elements to the set of elements - json = new FhirJsonSerializer(new SerializerSettings() { IncludeMandatoryInElementsSummary = true }) - .SerializeToDocument(obs, elements: new[] { "issued" }); +#pragma warning disable CS0618 // Type or member is obsolete + json = new FhirJsonSerializer() + .SerializeToDocument(obs, elements: ["issued"], includeMandatoryInElementsSummary: true); +#pragma warning restore CS0618 // Type or member is obsolete Assert.IsTrue(json.ContainsKey("issued")); Assert.IsTrue(json.ContainsKey("status")); @@ -619,17 +552,14 @@ public void IncludeMandatoryInElementsSummaryTest() [TestMethod] public void AllowParseDateWithDateTime() { - var patientJson = "{ \"resourceType\": \"Patient\", \"birthDate\": \"1991-02-03T11:22:33Z\" }"; - var parser = new FhirJsonParser(); - - // Assert.ThrowsException(() => parser.Parse(patientJson)); + const string patientJson = "{ \"resourceType\": \"Patient\", \"birthDate\": \"1991-02-03T11:22:33Z\" }"; #pragma warning disable CS0618 // Type or member is obsolete - parser = new FhirJsonParser(new ParserSettings { TruncateDateTimeToDate = true }); + var parser = new FhirJsonParser(new ParserSettings { TruncateDateTimeToDate = true }); #pragma warning restore CS0618 // Type or member is obsolete var patient = parser.Parse(patientJson); - Assert.AreEqual("1991-02-03", patient.BirthDate.ToString()); + Assert.AreEqual("1991-02-03", patient.BirthDate); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/SummarySerializationTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/SummarySerializationTests.cs index 498f423a51..4e0ee32650 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/SummarySerializationTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/SummarySerializationTests.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Xml.Linq; using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Tests.Serialization @@ -36,7 +37,7 @@ public void TestConstructSystemTextJsonSerializer() var p = new Patient { BirthDate = "1972-11-30", // present in both summary and full - Photo = new List() { new Attachment() { ContentType = "text/plain" } } + Photo = [new Attachment() { ContentType = "text/plain" }] }; @@ -50,15 +51,15 @@ public async Tasks.Task TestSummary() var p = new Patient { BirthDate = "1972-11-30", // present in both summary and full - Photo = new List() { new Attachment() { ContentType = "text/plain" } } + Photo = [new Attachment() { ContentType = "text/plain" }] }; - var full = await FhirXmlSerializer.SerializeToStringAsync(p); + var full = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(full.Contains("(); - q.Item.Add(new Questionnaire.ItemComponent() - { - LinkId = "linkid", - Text = new Markdown("TEXT") - }); + q.Item = + [ + new Questionnaire.ItemComponent() { LinkId = "linkid", Text = new Markdown("TEXT") } + ]; Assert.IsNull(q.Meta, "Meta element has not been created."); - var qfull = await FhirXmlSerializer.SerializeToStringAsync(q); + var qfull = FhirXmlSerializer.SerializeToString(q); Assert.IsNull(q.Meta, "Meta element should not be introduced here."); Console.WriteLine("summary: Fhir.Rest.SummaryType.False"); Console.WriteLine(qfull); @@ -87,7 +86,7 @@ public async Tasks.Task TestSummary() Assert.IsTrue(qfull.Contains(" t.System == "http://terminology.hl7.org/CodeSystem/v3-ObservationValue" && t.Code == "SUBSETTED").Count(), "Subsetted Tag should not still be there."); + Assert.AreEqual(0, q.Meta.Tag.Count(t => t.System == "http://terminology.hl7.org/CodeSystem/v3-ObservationValue" && t.Code == "SUBSETTED"), "Subsetted Tag should not still be there."); // Verify that reloading the content into an object... // make sure we accept the crappy output with empty groups var nav = await FhirXmlNode.ParseAsync(qText, new FhirXmlParsingSettings { PermissiveParsing = true }); var qInflate = FhirXmlParser.Parse(nav); - Assert.AreEqual(1, qInflate.Meta.Tag.Where(t => t.System == "http://terminology.hl7.org/CodeSystem/v3-ObservationValue" && t.Code == "SUBSETTED").Count(), "Subsetted Tag should not still be there."); + Assert.AreEqual(1, qInflate.Meta.Tag.Count(t => t.System == "http://terminology.hl7.org/CodeSystem/v3-ObservationValue" && t.Code == "SUBSETTED"), "Subsetted Tag should not still be there."); } [TestMethod] - public async Tasks.Task TestIncludeMandatory() + public void TestIncludeMandatory() { var l = new Library(); l.Type = new CodeableConcept { TextElement = new FhirString("testMandatoryComplexType") }; l.Id = "testId"; l.Language = "testLang"; - var summaryElements = await FhirXmlSerializer.SerializeToStringAsync(l, Fhir.Rest.SummaryType.Count); + var summaryElements = FhirXmlSerializer.SerializeToString(l, Fhir.Rest.SummaryType.Count); Assert.IsFalse(summaryElements.Contains("")); @@ -148,7 +147,7 @@ public async Tasks.Task TestIncludeMandatory() PreserveBundle = MaskingNodeSettings.PreserveBundleMode.All }); - var result = await customMaskingNode.ToXmlAsync(settings: new FhirXmlSerializationSettings()); + var result = customMaskingNode.ToXml(); Assert.IsFalse(result.Contains("")); Assert.IsTrue(result.Contains("")); @@ -170,57 +169,52 @@ public async Tasks.Task TestIncludeMandatory() PreserveBundle = MaskingNodeSettings.PreserveBundleMode.None }); - result = await customMaskingNodeForBundle.ToXmlAsync(settings: new FhirXmlSerializationSettings()); + result = customMaskingNodeForBundle.ToXml(); Assert.IsTrue(result.Contains("() { new Attachment() { ContentType = "text/plain" } } + Photo = [new Attachment() { ContentType = "text/plain" }] }; var elements = new[] { "photo" }; - var summaryElements = await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.False, elements: elements); + var summaryElements = FhirXmlSerializer.SerializeToString(p, SummaryType.False, elements: elements); Assert.IsFalse(summaryElements.Contains("(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.True, elements: elements)); - await ExceptionAssert.Throws(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.Count, elements: elements)); - await ExceptionAssert.Throws(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.Data, elements: elements)); - await ExceptionAssert.Throws(async () => await FhirXmlSerializer.SerializeToStringAsync(p, Fhir.Rest.SummaryType.Text, elements: elements)); } [TestMethod] - public async Tasks.Task TestWithMetadata() + public void TestWithMetadata() { var p = new Patient { BirthDate = "1972-11-30" }; - var pSum = await FhirXmlSerializer.SerializeToStringAsync(p, summary: Fhir.Rest.SummaryType.True); + var pSum = FhirXmlSerializer.SerializeToString(p, summary: SummaryType.True); Assert.IsNull(p.Meta, "Meta should not be there"); p.Meta = new Meta { VersionId = "v2" }; // introducing meta data ourselves. - pSum = await FhirXmlSerializer.SerializeToStringAsync(p, summary: Fhir.Rest.SummaryType.True); + pSum = FhirXmlSerializer.SerializeToString(p, summary: Fhir.Rest.SummaryType.True); Assert.IsNotNull(p.Meta, "Meta should still be there"); - Assert.AreEqual(0, p.Meta.Tag.Where(t => t.System == "http://terminology.hl7.org/CodeSystem/v3-ObservationValue" && t.Code == "SUBSETTED").Count(), "Subsetted Tag should not still be there."); + Assert.AreEqual(0, p.Meta.Tag.Count(t => t.System == "http://terminology.hl7.org/CodeSystem/v3-ObservationValue" && t.Code == "SUBSETTED"), "Subsetted Tag should not still be there."); } [TestMethod] - public async Tasks.Task TestBundleSummary() + public void TestBundleSummary() { var p = new Patient { @@ -233,19 +227,19 @@ public async Tasks.Task TestBundleSummary() b.Total = 1; b.Type = Bundle.BundleType.Searchset; - var full = await FhirXmlSerializer.SerializeToStringAsync(b); + var full = FhirXmlSerializer.SerializeToString(b); Assert.IsTrue(full.Contains(" data = new Dictionary - { - { "summary/bundle-summary-true.json", SummaryType.True }, - { "summary/bundle-summary-false.json", SummaryType.False }, - { "summary/bundle-summary-data.json", SummaryType.Data }, - { "summary/bundle-summary-text.json", SummaryType.Text }, - { "summary/bundle-summary-count.json", SummaryType.Count }, - { "summary/bundle-summary-true.xml", SummaryType.True }, - { "summary/bundle-summary-false.xml", SummaryType.False }, - { "summary/bundle-summary-data.xml", SummaryType.Data }, - { "summary/bundle-summary-text.xml", SummaryType.Text }, - { "summary/bundle-summary-count.xml", SummaryType.Count } - }; - var patientOne = new Patient { @@ -290,7 +280,7 @@ public async Tasks.Task TestBundleWithSummaryJson() Active = true, Text = new Narrative { Div = "
Another great blues player
", Status = Narrative.NarrativeStatus.Additional }, Meta = new Meta { VersionId = "bb-king" }, - Name = new List { new HumanName { Family = "King", Use = HumanName.NameUse.Nickname } } + Name = [new HumanName { Family = "King", Use = HumanName.NameUse.Nickname }] }; var bundle = new Bundle() @@ -298,76 +288,78 @@ public async Tasks.Task TestBundleWithSummaryJson() Id = "my-bundle", Total = 1803, Type = Bundle.BundleType.Searchset, - Entry = new List { - new Bundle.EntryComponent { Resource = patientOne, FullUrl = "http://base/Patient/patient-one", Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } }, - new Bundle.EntryComponent { Resource = patientTwo, FullUrl = "http://base/Patient/patient-two", Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } } + Entry = + [ + new() + { + Resource = patientOne, + FullUrl = "http://base/Patient/patient-one", + Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } + }, + new() + { + Resource = patientTwo, + FullUrl = "http://base/Patient/patient-two", + Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } } + ] }; - foreach (var pair in data) - { - var expectedFile = pair.Key; - var mode = pair.Value; - bool inJson = Path.GetExtension(expectedFile) == ".json"; - var actualData = inJson ? await FhirJsonSerializer.SerializeToStringAsync(bundle, mode) : - await FhirXmlSerializer.SerializeToStringAsync(bundle, mode); + var actualData = inJson ? FhirJsonSerializer.SerializeToString(bundle, mode) : + FhirXmlSerializer.SerializeToString(bundle, mode); var expectedData = TestDataHelper.ReadTestData(expectedFile); - actualData.Should().Be(expectedData); - } + + if(inJson) + JsonAssert.AreSame(expectedFile, expectedData, actualData); + else + XmlAssert.AreSame(expectedFile, XDocument.Parse(expectedData), XDocument.Parse(actualData)); } [TestMethod] - public async Tasks.Task TestResourceWithSummary() + [DataRow("summary/summary-true.json", SummaryType.True)] + [DataRow("summary/summary-false.json", SummaryType.False)] + [DataRow("summary/summary-data.json", SummaryType.Data)] + [DataRow("summary/summary-text.json", SummaryType.Text)] + [DataRow("summary/summary-true.xml", SummaryType.True)] + [DataRow("summary/summary-false.xml", SummaryType.False)] + [DataRow("summary/summary-data.xml", SummaryType.Data)] + [DataRow("summary/summary-text.xml", SummaryType.Text)] + public void TestResourceWithSummary(string expectedFile, SummaryType mode) { - Dictionary data = new Dictionary - { - { "summary/summary-true.json", SummaryType.True }, - { "summary/summary-false.json", SummaryType.False }, - { "summary/summary-data.json", SummaryType.Data }, - { "summary/summary-text.json", SummaryType.Text }, - { "summary/summary-true.xml", SummaryType.True }, - { "summary/summary-false.xml", SummaryType.False }, - { "summary/summary-data.xml", SummaryType.Data }, - { "summary/summary-text.xml", SummaryType.Text } - }; - - foreach (var pair in data) + var patientOne = new Patient { - var expectedFile = pair.Key; - var mode = pair.Value; - var patientOne = new Patient - { + Id = "patient-one", + Text = new Narrative { Status = Narrative.NarrativeStatus.Generated, Div = "
A great blues player
" }, + Meta = new Meta { ElementId = "eric-clapton", VersionId = "1234" }, - Id = "patient-one", - Text = new Narrative { Status = Narrative.NarrativeStatus.Generated, Div = "
A great blues player
" }, - Meta = new Meta { ElementId = "eric-clapton", VersionId = "1234" }, + Name = new List { new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official } }, - Name = new List { new HumanName { Family = "Clapton", Use = HumanName.NameUse.Official } }, + Active = true, + BirthDate = "2015-07-09", + Gender = AdministrativeGender.Male + }; - Active = true, - BirthDate = "2015-07-09", - Gender = AdministrativeGender.Male - }; + bool inJson = Path.GetExtension(expectedFile) == ".json"; + var actualData = inJson ? FhirJsonSerializer.SerializeToString(patientOne, mode) : + FhirXmlSerializer.SerializeToString(patientOne, mode); + var expectedData = TestDataHelper.ReadTestData(expectedFile); - // Properties with IsSummary == true -> Id, Meta, Active, BirthDate, Gender, Name - bool inJson = Path.GetExtension(expectedFile) == ".json"; - var actualData = inJson ? await FhirJsonSerializer.SerializeToStringAsync(patientOne, mode) : - await FhirXmlSerializer.SerializeToStringAsync(patientOne, mode); - var expectedData = TestDataHelper.ReadTestData(expectedFile); - Assert.AreEqual(expectedData, actualData, $"SummaryType.{mode} in file {pair.Key}"); - } + if(inJson) + JsonAssert.AreSame(expectedFile, expectedData, actualData); + else + XmlAssert.AreSame(expectedFile, XDocument.Parse(expectedData), XDocument.Parse(actualData)); } [TestMethod] - public async Tasks.Task TestIdInSummary() + public void TestIdInSummary() { var p = new Patient { Id = "test-id-1", BirthDate = "1972-11-30", // present in both summary and full - Photo = new List() { new Attachment() { ContentType = "text/plain", Creation = "45" } }, + Photo = [new Attachment() { ContentType = "text/plain", Creation = "45" }], ManagingOrganization = new ResourceReference() { Display = "temp org", Reference = "#temp" }, Text = new Narrative @@ -375,15 +367,12 @@ public async Tasks.Task TestIdInSummary() Div = "
Some test narrative
" }, Meta = new Meta(), - Contained = new List - { - new Organization() { Id = "temp", Name = "temp org", Active = true } - } + Contained = [new Organization() { Id = "temp", Name = "temp org", Active = true }] }; p.AddExtension("http://example.org/ext", new FhirString("dud")); - var full = await FhirXmlSerializer.SerializeToStringAsync(p); + var full = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(full.Contains("narrative")); Assert.IsTrue(full.Contains("dud")); Assert.IsTrue(full.Contains("temp org")); @@ -392,7 +381,7 @@ public async Tasks.Task TestIdInSummary() Assert.IsTrue(full.Contains(" XmlRoundTripAsync(T resource) where T : Resource var baseTestPath = CreateEmptyDir(); var xmlFile = Path.Combine(baseTestPath, "ObservationWithValueQuantityExample.xml"); - var xml = await new FhirXmlSerializer().SerializeToStringAsync(resource); + var xml = new FhirXmlSerializer().SerializeToString(resource); await File.WriteAllTextAsync(xmlFile, xml); xml = await File.ReadAllTextAsync(xmlFile); @@ -86,7 +86,7 @@ static async Tasks.Task JsonRoundTrip(T resource) where T : Resource var baseTestPath = CreateEmptyDir(); var jsonFile = Path.Combine(baseTestPath, "ObservationWithValueQuantityExample.json"); - var json = await new FhirJsonSerializer().SerializeToStringAsync(resource); + var json = new FhirJsonSerializer().SerializeToString(resource); await File.WriteAllTextAsync(jsonFile, json); json = await File.ReadAllTextAsync(jsonFile); @@ -103,4 +103,4 @@ static string CreateEmptyDir() return baseTestPath; } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.json b/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.json index 64623a13f0..dc4a889d65 100644 --- a/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.json +++ b/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.json @@ -1 +1 @@ -{"resourceType":"Patient","id":"patient-one","meta":{"id":"eric-clapton","versionId":"1234","tag":[{"system":"http://hl7.org/fhir/v3/ObservationValue","code":"SUBSETTED"},{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationValue","code":"SUBSETTED"}]},"active":true,"name":[{"use":"official","family":"Clapton"}],"gender":"male","birthDate":"2015-07-09"} \ No newline at end of file +{"resourceType":"Patient","id":"patient-one","meta":{"versionId":"1234","tag":[{"system":"http://hl7.org/fhir/v3/ObservationValue","code":"SUBSETTED"},{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationValue","code":"SUBSETTED"}]},"active":true,"name":[{"use":"official","family":"Clapton"}],"gender":"male","birthDate":"2015-07-09"} diff --git a/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.xml b/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.xml index 5cae121e4e..28887c8c73 100644 --- a/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.xml +++ b/src/Hl7.Fhir.Shared.Tests/TestData/summary/summary-true.xml @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs b/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs index 843f80b8ea..3ead98b46d 100644 --- a/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs +++ b/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs @@ -183,7 +183,7 @@ private static IScopedNode mockResolver(string url) var type = ModelInfo.GetTypeForFhirType(ri.ResourceType)!; DomainResource res = Activator.CreateInstance(type) as DomainResource; res!.Id = ri.Id; - return res.ToElementNode(); + return res.ToPocoNode(); } return null; } diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/ElementModel/TypedElementExtensions.cs b/src/Hl7.Fhir.Shims.STU3AndUp/ElementModel/TypedElementExtensions.cs index 541cd7326b..32a25810b3 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/ElementModel/TypedElementExtensions.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/ElementModel/TypedElementExtensions.cs @@ -8,17 +8,14 @@ #nullable enable -using Hl7.Fhir.ElementModel.Adapters; -using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; namespace Hl7.Fhir.ElementModel { - public static class TypedElementExtensions + public static class ShimsTypedElementExtensions { /// /// Creates an adapter which implements ITypedElement on top of a POCO instance, using the legacy ElementNode stack. @@ -27,8 +24,8 @@ public static class TypedElementExtensions /// The name you wish to have at the root of the tree. This will determine e.g. the root element name for serialization. /// If none is given, the type of the underlying poco will be used. public static ITypedElement ToTypedElementLegacy(this Base @base, string? rootName = null) - => new PocoElementNode(ModelInfo.ModelInspector, @base, rootName); - + => @base.ToTypedElementLegacy(ModelInfo.ModelInspector, rootName); + /// /// Creates an adapter which implements ITypedElement on top of a POCO instance, with explicit version-specific metadata. /// @@ -39,17 +36,12 @@ public static ITypedElement ToTypedElementLegacy(this Base @base, string? rootNa /// In the meantime, you can restore the old behaviour with a call to #if NETSTANDARD2_1 [Obsolete("The implementation of this method has changed to use our new model stack. If you want to try the new behaviour, "+ - "either ignore this warning or call ToElementNode(). For reverting to the old behaviour, call .ToTypedElementLegacy()")] + "either ignore this warning or call ToPocoNode(). For reverting to the old behaviour, call .ToTypedElementLegacy()")] #else [Experimental("SDK0001")] #endif - public static ITypedElement ToTypedElement(this Base @base, string? rootName = null) - { - var node = PocoNodeOrList.Root(@base, rootName); - ((IAnnotatable)node).AddAnnotation(ModelInfo.ModelInspector); - - return node; - } + public static ITypedElement ToTypedElement(this Base @base, string? rootName = null) => + @base.ToTypedElement(ModelInfo.ModelInspector, rootName); } } #nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs b/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs index 94c0de2dc4..a53b73a2ee 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/FhirPath/FhirPathExtensions.cs @@ -47,23 +47,23 @@ public static class FhirPathExtensions /// public static IEnumerable Select(this Base input, string expression, FhirEvaluationContext? ctx = null) - => CACHE.Select(input.ToElementNode(), expression, ctx ?? new FhirEvaluationContext()).ToFhirValues(); + => CACHE.Select(input.ToPocoNode(), expression, ctx ?? new FhirEvaluationContext()).ToFhirValues(); /// public static object? Scalar(this Base input, string expression, FhirEvaluationContext? ctx = null) - => CACHE.Scalar(input.ToElementNode(), expression, ctx ?? new FhirEvaluationContext()); + => CACHE.Scalar(input.ToPocoNode(), expression, ctx ?? new FhirEvaluationContext()); /// public static bool Predicate(this Base input, string expression, FhirEvaluationContext? ctx = null) - => CACHE.Predicate(input.ToElementNode(), expression, ctx ?? new FhirEvaluationContext()); + => CACHE.Predicate(input.ToPocoNode(), expression, ctx ?? new FhirEvaluationContext()); /// public static bool IsTrue(this Base input, string expression, FhirEvaluationContext? ctx = null) - => CACHE.IsTrue(input.ToElementNode(), expression, ctx ?? new FhirEvaluationContext()); + => CACHE.IsTrue(input.ToPocoNode(), expression, ctx ?? new FhirEvaluationContext()); /// public static bool IsBoolean(this Base input, string expression, bool value, FhirEvaluationContext? ctx = null) - => CACHE.IsBoolean(input.ToElementNode(), expression, value, ctx ?? new FhirEvaluationContext()); + => CACHE.IsBoolean(input.ToPocoNode(), expression, value, ctx ?? new FhirEvaluationContext()); } } diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoDeserializer.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoDeserializer.cs index 4bb454ad2a..29603b160d 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoDeserializer.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoDeserializer.cs @@ -18,7 +18,7 @@ public FhirJsonPocoDeserializer() : base(ModelInfo.ModelInspector) { } /// Construct a new FHIR Json deserializer, based on the currently used FHIR version. /// /// Deserialization settings - public FhirJsonPocoDeserializer(FhirJsonPocoDeserializerSettings settings) : base(ModelInfo.ModelInspector, settings) { } + public FhirJsonPocoDeserializer(FhirJsonConverterOptions settings) : base(ModelInfo.ModelInspector, settings) { } /// [Obsolete("Please use FhirJsonPocoDeserializer() if you are using a single version of FHIR or BaseFhirJsonPocoDeserializer if you want to use multiple versions of FHIR")] @@ -34,13 +34,13 @@ public FhirJsonPocoDeserializer(ModelInspector inspector) : base(inspector) /// [Obsolete("Please use FhirJsonPocoDeserializer() if you are using a single version of FHIR or BaseFhirJsonPocoDeserializer if you want to use multiple versions of FHIR")] - public FhirJsonPocoDeserializer(Assembly assembly, FhirJsonPocoDeserializerSettings settings) : base(assembly, settings) + public FhirJsonPocoDeserializer(Assembly assembly, FhirJsonConverterOptions settings) : base(assembly, settings) { } /// [Obsolete("Please use FhirJsonPocoDeserializer() if you are using a single version of FHIR or BaseFhirJsonPocoDeserializer if you want to use multiple versions of FHIR")] - public FhirJsonPocoDeserializer(ModelInspector inspector, FhirJsonPocoDeserializerSettings settings) : base(inspector, settings) + public FhirJsonPocoDeserializer(ModelInspector inspector, FhirJsonConverterOptions settings) : base(inspector, settings) { } } diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoSerializer.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoSerializer.cs index 423cf21529..4ddab46380 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoSerializer.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonPocoSerializer.cs @@ -1,41 +1,9 @@ #nullable enable using Hl7.Fhir.Model; -using Hl7.Fhir.Specification; using System; -namespace Hl7.Fhir.Serialization -{ - public class FhirJsonPocoSerializer : BaseFhirJsonPocoSerializer - { - /// - [Obsolete("Please use FhirJsonPocoSerializer() if you are using a single version of FHIR or BaseFhirJsonPocoSerializer if you want to use multiple versions of FHIR")] - public FhirJsonPocoSerializer(FhirRelease release) : base(release) - { - } +namespace Hl7.Fhir.Serialization; - /// - [Obsolete("Please use FhirJsonPocoSerializer() if you are using a single version of FHIR or BaseFhirJsonPocoSerializer if you want to use multiple versions of FHIR")] - public FhirJsonPocoSerializer(FhirRelease release, FhirJsonPocoSerializerSettings settings) : base(release, settings) - { - } - - /// - /// Construct a new FHIR Json serializer, based on the currently used FHIR version. - /// - public FhirJsonPocoSerializer() : base(ModelInfo.ModelInspector.FhirRelease) - { - } - - /// - /// Construct a new FHIR Json serializer, based on the currently used FHIR version. - /// - /// Serialization settings - public FhirJsonPocoSerializer(FhirJsonPocoSerializerSettings settings) : base(ModelInfo.ModelInspector.FhirRelease, settings) - { - } - - } -} - -#nullable restore +[Obsolete("This class has been replaced by the equivalent FhirXmlSerializer class.")] +public class FhirJsonPocoSerializer() : BaseFhirJsonPocoSerializer(ModelInfo.ModelInspector); \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonSerializer.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonSerializer.cs index fa7329af17..87f2c462a8 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonSerializer.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonSerializer.cs @@ -10,13 +10,9 @@ using Hl7.Fhir.Model; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +public class FhirJsonSerializer() : BaseFhirJsonSerializer(ModelInfo.ModelInspector) { - public class FhirJsonSerializer : CommonFhirJsonSerializer - { - public FhirJsonSerializer(SerializerSettings? settings = null) : base(ModelInfo.ModelInspector, settings) - { - } - } -} -#nullable restore \ No newline at end of file + public static readonly FhirJsonSerializer Default = new(); +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoSerializer.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoSerializer.cs index 3c69ae971d..92866518ae 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoSerializer.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoSerializer.cs @@ -1,29 +1,9 @@ #nullable enable using Hl7.Fhir.Model; -using Hl7.Fhir.Specification; using System; -namespace Hl7.Fhir.Serialization -{ - public class FhirXmlPocoSerializer : BaseFhirXmlPocoSerializer - { +namespace Hl7.Fhir.Serialization; - /// - /// Construct a new FHIR XML serializer, based on the currently used FHIR version. - /// - public FhirXmlPocoSerializer() : base(ModelInfo.ModelInspector.FhirRelease) - { - - } - - - /// - [Obsolete("Please use BaseFhirXmlPocoSerializer if you want to use multiple versions of FHIR")] - public FhirXmlPocoSerializer(FhirRelease release) : base(release) - { - } - } -} - -#nullable restore +[Obsolete("This class has been replaced by the equivalent FhirXmlSerializer class.")] +public class FhirXmlPocoSerializer() : BaseFhirXmlPocoSerializer(ModelInfo.ModelInspector); \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlSerializer.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlSerializer.cs index ef52531416..9274fd0493 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlSerializer.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlSerializer.cs @@ -10,13 +10,9 @@ using Hl7.Fhir.Model; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +public class FhirXmlSerializer() : BaseFhirXmlSerializer(ModelInfo.ModelInspector) { - public class FhirXmlSerializer : CommonFhirXmlSerializer - { - public FhirXmlSerializer(SerializerSettings? settings = null) : base(ModelInfo.ModelInspector, settings) - { - } - } -} -#nullable restore \ No newline at end of file + public static readonly FhirXmlSerializer Default = new(); +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/JsonSerializationOptionsExtensions.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/JsonSerializationOptionsExtensions.cs index 0c5c10f19d..dc7122088a 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/JsonSerializationOptionsExtensions.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/JsonSerializationOptionsExtensions.cs @@ -33,29 +33,15 @@ public static class JsonSerializerOptionsExtensions #else [System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] #endif - public static JsonSerializerOptions ForCdsHooks(this JsonSerializerOptions options, FhirJsonPocoSerializerSettings? serializerSettings = null, - FhirJsonPocoDeserializerSettings? deserializerSettings = null) => - options.ForCdsHooks(ModelInfo.ModelInspector, serializerSettings ?? new FhirJsonPocoSerializerSettings(), - deserializerSettings ?? new FhirJsonPocoDeserializerSettings()); + public static JsonSerializerOptions ForCdsHooks(this JsonSerializerOptions options, + FhirJsonConverterOptions? converterOptions = null) => + options.ForCdsHooks(ModelInfo.ModelInspector, + converterOptions ?? new FhirJsonConverterOptions()); - /// public static JsonSerializerOptions ForFhir( this JsonSerializerOptions options, - FhirJsonPocoSerializerSettings serializerSettings) => - options.ForFhir(ModelInfo.ModelInspector, serializerSettings); - - /// - public static JsonSerializerOptions ForFhir( - this JsonSerializerOptions options, - FhirJsonPocoDeserializerSettings deserializerSettings) => - options.ForFhir(ModelInfo.ModelInspector, deserializerSettings); - - /// - public static JsonSerializerOptions ForFhir( - this JsonSerializerOptions options, - FhirJsonPocoSerializerSettings serializerSettings, - FhirJsonPocoDeserializerSettings deserializerSettings) => - options.ForFhir(ModelInfo.ModelInspector, serializerSettings, deserializerSettings); + FhirJsonConverterOptions converterOptions) => + options.ForFhir(ModelInfo.ModelInspector, converterOptions); } } diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/PocoSerializationExtensions.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/PocoSerializationExtensions.cs index 3874c44bdc..ca3291a3f4 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/PocoSerializationExtensions.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/PocoSerializationExtensions.cs @@ -6,65 +6,72 @@ * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE */ +#nullable enable using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System; using System.Xml; using System.Xml.Linq; using Tasks = System.Threading.Tasks; -namespace Hl7.Fhir.Serialization -{ - public static class PocoSerializationExtensions - { - /// - public static string ToJson(this Base source, FhirJsonSerializationSettings settings = null) => - source.ToTypedElement().ToJson(settings); - - public static async Tasks.Task ToJsonAsync(this Base source, FhirJsonSerializationSettings settings = null) => - await source.ToTypedElement().ToJsonAsync(settings).ConfigureAwait(false); - - /// - public static byte[] ToJsonBytes(this Base source, FhirJsonSerializationSettings settings = null) => - source.ToTypedElement().ToJsonBytes(settings); - - public static async Tasks.Task ToJsonBytesAsync(this Base source, FhirJsonSerializationSettings settings = null) => - await source.ToTypedElement().ToJsonBytesAsync(settings).ConfigureAwait(false); - - /// - public static void WriteTo(this Base source, JsonWriter destination, FhirJsonSerializationSettings settings = null) => - source.ToTypedElement().WriteTo(destination, settings); - - public static async Tasks.Task WriteToAsync(this Base source, JsonWriter destination, FhirJsonSerializationSettings settings = null) => - await source.ToTypedElement().WriteToAsync(destination, settings).ConfigureAwait(false); - - public static JObject ToJObject(this Base source, FhirJsonSerializationSettings settings = null) => - source.ToTypedElement().ToJObject(settings); +namespace Hl7.Fhir.Serialization; - /// - public static string ToXml(this Base source, FhirXmlSerializationSettings settings = null) => - source.ToTypedElement().ToXml(settings); - - public static async Tasks.Task ToXmlAsync(this Base source, FhirXmlSerializationSettings settings = null) => - await source.ToTypedElement().ToXmlAsync(settings).ConfigureAwait(false); - - /// - public static byte[] ToXmlBytes(this Base source, FhirXmlSerializationSettings settings = null) => - source.ToTypedElement().ToXmlBytes(settings); - - public static async Tasks.Task ToXmlBytesAsync(this Base source, FhirXmlSerializationSettings settings = null) => - await source.ToTypedElement().ToXmlBytesAsync(settings).ConfigureAwait(false); - - /// - public static void WriteTo(this Base source, XmlWriter destination, FhirXmlSerializationSettings settings = null) => - source.ToTypedElement().WriteTo(destination, settings); - - public static async Tasks.Task WriteToAsync(this Base source, XmlWriter destination, FhirXmlSerializationSettings settings = null) => - await source.ToTypedElement().WriteToAsync(destination, settings).ConfigureAwait(false); - - public static XDocument ToXDocument(this Base source, FhirXmlSerializationSettings settings = null) => - source.ToTypedElement().ToXDocument(settings); - } +/// +/// Provides extension methods on POCOs to serialize to Xml and Json. +/// +public static class PocoSerializationExtensions +{ + /// + /// Serializes the given POCO into a FHIR Json string. + /// + /// The instance to serialize. + /// Formats and indents the serialized Json. + public static string ToJson(this Base instance, bool pretty = false) => + FhirJsonSerializer.Default.SerializeToString(instance, pretty); + + /// + /// Serializes the given POCO into a FHIR Xml string. + /// + /// The instance to serialize. + /// Formats and indents the serialized Json. + public static string ToXml(this Base instance, bool pretty = false) => + FhirXmlSerializer.Default.SerializeToString(instance); + + // 20241217 + [Obsolete("We're cleaning up the POCO API surface, please use FhirJsonSerializer.Default.SerializeToBytes() instead.")] + public static byte[] ToJsonBytes(this Base source, bool pretty = false) => + FhirJsonSerializer.Default.SerializeToBytes(source, pretty); + + // 20241217 + [Obsolete("We're phasing out Newtonsoft in favor of System.Text.Json, please use FhirJsonSerializer.Default.Serialize() instead.")] + public static void WriteTo(this Base source, JsonWriter destination) => + source.ToTypedElement().WriteTo(destination); + + // 20241217 + [Obsolete("We're phasing out Newtonsoft in favor of System.Text.Json, please use FhirJsonSerializer.Default.Serialize() instead.")] + public static async Tasks.Task WriteToAsync(this Base source, JsonWriter destination) => + await source.ToTypedElement().WriteToAsync(destination).ConfigureAwait(false); + + + [Obsolete("The new serializers are written against non-async APIs, so this async call is actually sync. Change to a non-async call.")] +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + public static async Tasks.Task ToXmlAsync(this Base source, bool pretty = false) => +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + FhirXmlSerializer.Default.SerializeToString(source); + + // 20241217 + [Obsolete("We're cleaning up the POCO API surface, please use FhirXmlSerializer.Default.SerializeToBytes() instead.")] + public static byte[] ToXmlBytes(this Base source, bool pretty = false, SerializationFilter? filter = null) => + FhirXmlSerializer.Default.SerializeToBytes(source, pretty, filter); + + // 20241217 + [Obsolete("We're cleaning up the POCO API surface, please use FhirXmlSerializer.Default.Serialize() instead.")] + public static void WriteTo(this Base source, XmlWriter destination, SerializationFilter? filter = null) => + FhirXmlSerializer.Default.Serialize(source, destination, filter); + + // 20241217 + [Obsolete("We're cleaning up the POCO API surface, please use and FhirXmlSerializer.Default.SerializeToDocument() instead.")] + public static XDocument ToXDocument(this Base source) => FhirXmlSerializer.Default.SerializeToDocument(source); } \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.R4.Tests/Hl7.Fhir.Specification.R4.Tests.csproj b/src/Hl7.Fhir.Specification.R4.Tests/Hl7.Fhir.Specification.R4.Tests.csproj index 10af101d76..63408d8cb9 100644 --- a/src/Hl7.Fhir.Specification.R4.Tests/Hl7.Fhir.Specification.R4.Tests.csproj +++ b/src/Hl7.Fhir.Specification.R4.Tests/Hl7.Fhir.Specification.R4.Tests.csproj @@ -16,7 +16,6 @@ - @@ -27,4 +26,4 @@ - \ No newline at end of file + diff --git a/src/Hl7.Fhir.Specification.R4B.Tests/Hl7.Fhir.Specification.R4B.Tests.csproj b/src/Hl7.Fhir.Specification.R4B.Tests/Hl7.Fhir.Specification.R4B.Tests.csproj index c97f090504..ea4152de50 100644 --- a/src/Hl7.Fhir.Specification.R4B.Tests/Hl7.Fhir.Specification.R4B.Tests.csproj +++ b/src/Hl7.Fhir.Specification.R4B.Tests/Hl7.Fhir.Specification.R4B.Tests.csproj @@ -16,7 +16,6 @@ - @@ -27,4 +26,4 @@ - \ No newline at end of file + diff --git a/src/Hl7.Fhir.Specification.R5.Tests/Hl7.Fhir.Specification.R5.Tests.csproj b/src/Hl7.Fhir.Specification.R5.Tests/Hl7.Fhir.Specification.R5.Tests.csproj index 936e7fde38..d23cee11b6 100644 --- a/src/Hl7.Fhir.Specification.R5.Tests/Hl7.Fhir.Specification.R5.Tests.csproj +++ b/src/Hl7.Fhir.Specification.R5.Tests/Hl7.Fhir.Specification.R5.Tests.csproj @@ -21,7 +21,6 @@ - @@ -32,4 +31,4 @@ - \ No newline at end of file + diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs index e85807f1a6..70c6eeb39f 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -1095,8 +1095,8 @@ private async Tasks.Task generateSnapshot(StructureDefiniti // { var tempPath = Path.GetTempPath(); var xmlSer = new FhirXmlSerializer(); - await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-source.xml"), await xmlSer.SerializeToStringAsync(original)); - await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-dest.xml"), await xmlSer.SerializeToStringAsync(expanded)); + await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-source.xml"), xmlSer.SerializeToString(original)); + await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-dest.xml"), xmlSer.SerializeToString(expanded)); // } // Assert.IsTrue(areEqual); @@ -8458,7 +8458,6 @@ public async Tasks.Task TestMergingAPreviouslyRemovedElement() var valueQuantityEld = elementDefinitions.FirstOrDefault(eld => "Observation.value[x]:valueQuantity".Equals((eld.ElementId))); Assert.IsNull(valueQuantityEld); - } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs index 1a3a0aa963..df0143daf6 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs @@ -469,7 +469,7 @@ async Tasks.Task OpenStream(string filePath) var dupId = res.Id; var rootId = Guid.NewGuid().ToString(); res.Id = rootId; - _ = await new FhirXmlSerializer().SerializeToStringAsync(res); + _ = new FhirXmlSerializer().SerializeToString(res); var dupFilePath = Path.Combine(fullSubFolderPath, srcFile); Assert.IsTrue(File.Exists(dupFilePath)); diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ConformanceSourceTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ConformanceSourceTests.cs index 18862af9ef..0937567357 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ConformanceSourceTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ConformanceSourceTests.cs @@ -451,12 +451,12 @@ public async Tasks.Task TestThreadSafety() } [TestMethod] - public async Tasks.Task TestRefreshAll() => await TestRefreshAsync(true); + public void TestRefreshAll() => testRefresh(true); [TestMethod] - public async Tasks.Task TestRefreshFile() => await TestRefreshAsync(false); + public void TestRefreshFile() => testRefresh(false); - async Tasks.Task TestRefreshAsync(bool refreshAll) + private static void testRefresh(bool refreshAll) { // Create a temporary folder with a single artifact file const string srcFileName = "TestPatient.xml"; @@ -530,7 +530,7 @@ void Refresh(params string[] files) Assert.AreEqual(patient.Id, "pat1"); patient.Id = "CHANGED"; var serializer = new FhirXmlSerializer(); - var xml = await serializer.SerializeToStringAsync(patient); + var xml = serializer.SerializeToString(patient); File.WriteAllText(tmpFilePath, xml); // Verify that Refresh updates the summary information @@ -552,7 +552,7 @@ void Refresh(params string[] files) } [TestMethod] - public async Tasks.Task TestParserSettings() + public void TestParserSettings() { // Create an invalid patient resource on disk var obs = new Observation() @@ -561,14 +561,13 @@ public async Tasks.Task TestParserSettings() Comment = " " // Illegal empty value }; var nav = obs.ToTypedElement(); - var xml = await nav.ToXmlAsync(); + var xml = nav.ToXml(); var folderPath = Path.Combine(Path.GetTempPath(), "TestDirectorySource"); var filePath = Path.Combine(folderPath, "TestPatient.xml"); try { - Directory.CreateDirectory(folderPath); File.WriteAllText(filePath, xml); diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs index 797b6accc7..758d8b5bc2 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs @@ -316,7 +316,7 @@ public void TestSetupIsOnce() // [WMR 20160823] NEW - Verify FileDirectoryArtifactSource & ResolvingConflictException [TestMethod] - public async Tasks.Task TestCanonicalUrlConflicts() + public void TestCanonicalUrlConflicts() { //const string srcFileName = "extension-definitions.xml"; const string dupFileName = "diagnosticorder-reason-duplicate"; @@ -332,7 +332,7 @@ public async Tasks.Task TestCanonicalUrlConflicts() // Save back to disk to create a conflicting duplicate var b = new Bundle(); b.AddResourceEntry(ext, url); - var xml = await new FhirXmlSerializer().SerializeToStringAsync(b); + var xml = FhirXmlSerializer.Default.SerializeToString(b); var filePath = Path.Combine(DirectorySource.SpecificationDirectory, dupFileName) + ".xml"; var filePath2 = Path.Combine(DirectorySource.SpecificationDirectory, dupFileName) + "2.xml"; File.WriteAllText(filePath, xml); diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs index d01454c1a4..d1bb180faa 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/StructureDefinitionWalkerTests.cs @@ -122,7 +122,7 @@ public async Tasks.Task WalkAcrossInlineExtension() nav.JumpToFirst("Patient.communication"); nav.MoveToNextSlice(); - var fortest = await nav.StructureDefinition.ToXmlAsync(); + var fortest = nav.StructureDefinition.ToXml(); var walker = new StructureDefinitionWalker(nav, _source); var elem = walker.Extension("http://hl7.org/fhir/StructureDefinition/patient-proficiency"); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Hl7.Fhir.Specification.Shared.Tests.projitems b/src/Hl7.Fhir.Specification.Shared.Tests/Hl7.Fhir.Specification.Shared.Tests.projitems index d44379881d..0e391ce3fd 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Hl7.Fhir.Specification.Shared.Tests.projitems +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Hl7.Fhir.Specification.Shared.Tests.projitems @@ -21,7 +21,6 @@ - diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs index 25dc62195c..30992d799f 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs @@ -58,17 +58,12 @@ public class SnapshotGeneratorManifestTests static readonly FhirXmlParsingSettings _fhirXmlParserSettings = new FhirXmlParsingSettings() { - PermissiveParsing = false + PermissiveParsing = true }; - //static readonly FhirJsonParsingSettings _fhirJsonParserSettings = new FhirJsonParsingSettings() - //{ - // PermissiveParsing = false - //}; - static readonly ParserSettings _parserSettings = new ParserSettings() { - PermissiveParsing = false + PermissiveParsing = true }; static readonly DirectorySourceSettings _dirSourceSettings = new DirectorySourceSettings() @@ -87,14 +82,9 @@ public class SnapshotGeneratorManifestTests GenerateSnapshotForExternalProfiles = true }; - static readonly SerializerSettings _serializerSettings = new SerializerSettings() - { - Pretty = true - }; - static readonly FhirXmlParser _fhirXmlParser = new FhirXmlParser(_parserSettings); static readonly FhirJsonParser _fhirJsonParser = new FhirJsonParser(_parserSettings); - static readonly FhirXmlSerializer _fhirXmlSerializer = new FhirXmlSerializer(_serializerSettings); + static readonly FhirXmlSerializer _fhirXmlSerializer = new FhirXmlSerializer(); string _testPath; DirectorySource _dirSource; @@ -679,7 +669,7 @@ static void SaveOutput(string id, StructureDefinition output) static void Save(string filePath, Base output) { - var xml = _fhirXmlSerializer.SerializeToString(output); + var xml = _fhirXmlSerializer.SerializeToString(output, pretty: true); File.WriteAllText(filePath, xml); } @@ -793,8 +783,8 @@ public SnapshotEvaluationContext( TestResolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); if (input is null) { throw new ArgumentNullException(nameof(input)); } if (generated is null) { throw new ArgumentNullException(nameof(generated)); } - Input = input.ToElementNode(); - Generated = generated.ToElementNode(); + Input = input.ToPocoNode(); + Generated = generated.ToPocoNode(); Id = id ?? throw new ArgumentNullException(nameof(id)); Assert.AreEqual(id, generated.Id); this.Tracer = this.Trace; @@ -841,7 +831,7 @@ IScopedNode Fixture(string name) { filePath = Path.ChangeExtension(filePath, "json"); } - return Load(filePath).ToElementNode(); + return Load(filePath).ToPocoNode(); } // Otherwise assume name refers to a core resource, e.g. 'patient' @@ -850,7 +840,7 @@ IScopedNode Fixture(string name) if (!(typeName is null)) { #pragma warning disable CS0618 // Type or member is obsolete - return TestResolver.FindStructureDefinitionForCoreType(typeName).ToElementNode(); + return TestResolver.FindStructureDefinitionForCoreType(typeName).ToPocoNode(); #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs index 5dd81d8af5..ba478bba0e 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -1209,8 +1209,8 @@ private async Tasks.Task generateSnapshot(StructureDefiniti // { var tempPath = Path.GetTempPath(); var xmlSer = new FhirXmlSerializer(); - await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-source.xml"), await xmlSer.SerializeToStringAsync(original)); - await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-dest.xml"), await xmlSer.SerializeToStringAsync(expanded)); + await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-source.xml"), xmlSer.SerializeToString(original)); + await File.WriteAllTextAsync(Path.Combine(tempPath, "snapshotgen-dest.xml"), xmlSer.SerializeToString(expanded)); // } // Assert.IsTrue(areEqual); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTypeSlicingTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTypeSlicingTests.cs deleted file mode 100644 index d826b89e02..0000000000 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorTypeSlicingTests.cs +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright (c) 2019, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -// [WMR 20190828] OBSOLETE, instead use SnapshotGeneratorManifestTests -#if false - -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Specification.Snapshot; -using System.Linq; -using System.Diagnostics; -using System.Collections.Generic; -using System; -using System.IO; -using Hl7.Fhir.Specification.Navigation; - -namespace Hl7.Fhir.Specification.Tests -{ - // [WMR 20190807] OBSOLETE - See SnapshotGeneratorManifestTests - - - [TestClass, TestCategory("Snapshot"), Ignore] -#if PORTABLE45 - public class PortableSnapshotGeneratorTypeSlicingTest -#else - public class SnapshotGeneratorTypeSlicingTest -#endif - { - static readonly FhirXmlSerializer serializer = new FhirXmlSerializer(new SerializerSettings() { Pretty = true, AppendNewLine = true }); - - SnapshotGenerator _generator; - DirectorySource _inputDirSource, _expectedDirSource; - TimingSource _inputSource, _expectedSource; - IResourceResolver _inputResolver, _expectedResolver; - - ElementDefinitionNavigator _DomainResourceNavigator; - ElementDefinitionNavigator _ExtensionNavigator; - - readonly SnapshotGeneratorSettings _settings = new SnapshotGeneratorSettings() - { - // Throw on unresolved profile references; must include in TestData folder - GenerateSnapshotForExternalProfiles = true, - ForceRegenerateSnapshots = true, - GenerateExtensionsOnConstraints = false, - GenerateAnnotationsOnConstraints = false, - GenerateElementIds = true // STU3 - }; - - static (DirectorySource dirSource, TimingSource timingSource, IResourceResolver resolver) InitializeSource(string mask) - { - var dirSource = new DirectorySource("TestData/snapshot-test/Type Slicing", - new DirectorySourceSettings - { - IncludeSubDirectories = false, - Includes = new string[] { mask } - } - ); - - var timingSource = new TimingSource(dirSource); - var resolver = new CachedResolver( - new MultiResolver( - new ZipSource("specification.zip"), - timingSource)); - - return (dirSource, timingSource, resolver); - } - - [TestInitialize] - public void Setup() - { - FhirPath.ElementNavFhirExtensions.PrepareFhirSymbolTableFunctions(); - - (_inputDirSource, _inputSource, _inputResolver) = InitializeSource("*-input.xml"); - (_expectedDirSource, _expectedSource, _expectedResolver) = InitializeSource("*-expected.xml"); - - _generator = new SnapshotGenerator(_inputResolver, _settings); - - _DomainResourceNavigator = ElementDefinitionNavigator.ForSnapshot(_inputResolver.FindStructureDefinitionForCoreType(FHIRAllTypes.DomainResource)); - _ExtensionNavigator = ElementDefinitionNavigator.ForSnapshot(_inputResolver.FindStructureDefinitionForCoreType(FHIRAllTypes.Extension)); - } - - // [WMR 20190723] Unit tests for type slicing, using Grahame's example profiles - // Source: https://github.com/hapifhir/org.hl7.fhir.core/tree/master/org.hl7.fhir.r5/src/test/resources/snapshot-generation - - StructureDefinition Load(DirectorySource dirSource, string name) - { - var summaries = dirSource.ListSummaries(ResourceType.StructureDefinition); - var summary = summaries.FirstOrDefault(s => s.GetConformanceName() == name); - if (!(summary is null)) - { - return dirSource.LoadBySummary(summary); - } - return null; - } - - StructureDefinition LoadInput(string name) => Load(_inputDirSource, name); - - StructureDefinition LoadExpected(string name) => Load(_expectedDirSource, name); - - bool compareElementValues(string elementId, string propertyName, string expected, string actual) - { - var result = StringComparer.Ordinal.Equals(expected, actual); - Debug.WriteLineIf(!result, $"MISMATCH {elementId}::{propertyName} - expected='{expected}', actual='{actual}'"); - return result; - } - - bool compareElementValues(string elementId, string propertyName, Base expected, Base actual) - { - var result = expected is null ? actual is null : expected.IsExactly(actual); - Debug.WriteLineIf(!result, $"MISMATCH {elementId}::{propertyName} - expected='{expected}', actual='{actual}'"); - return result; - } - - bool compareElementValues(string elementId, string propertyName, T expected, T actual) - where T : List - where S : Base - { - var result = expected is null ? actual is null : expected.Count == actual.Count; - - if (result) - { - for (int i = 0; i < expected.Count; i++) - { - result &= compareElementValues(elementId, propertyName, expected[i], actual[i]); - } - } - - return result; - } - - bool compareElements(ElementDefinition expected, ElementDefinition actual) - { - if (expected is null) { return actual is null; } - else if (actual is null) { return false; } - - bool result = true; - - var elemId = expected.ElementId; - - //result &= compareElementValues>, Code>(elemId, nameof(ElementDefinition.Representation), expected.RepresentationElement, actual.RepresentationElement); - result &= compareElementValues(elemId, nameof(ElementDefinition.SliceName), expected.SliceNameElement, actual.SliceNameElement); - result &= compareElementValues(elemId, nameof(ElementDefinition.SliceIsConstraining), expected.SliceIsConstrainingElement, actual.SliceIsConstrainingElement); - //result &= compareElementValues(elemId, nameof(ElementDefinition.Label), expected.LabelElement, actual.LabelElement); - //result &= compareElementValues, Coding>(elemId, nameof(ElementDefinition.Code), expected.Code, actual.Code); - - // [WMR 20190723] HACK - // .NET FHIR API - .extension elements inherit slicing component from Element.extension - // Grahame - .extension elements do NOT inherit slicing component - - if (!ElementDefinitionNavigator.IsExtensionPath(expected.Path)) - { - result &= compareElementValues(elemId, nameof(ElementDefinition.Slicing), expected.Slicing, actual.Slicing); - } - - result &= compareElementValues(elemId, nameof(ElementDefinition.Short), expected.ShortElement, actual.ShortElement); - result &= compareElementValues(elemId, nameof(ElementDefinition.Min), expected.MinElement, actual.MinElement); - result &= compareElementValues(elemId, nameof(ElementDefinition.Max), expected.MaxElement, actual.MaxElement); - result &= compareElementValues(elemId, nameof(ElementDefinition.Binding), expected.Binding, actual.Binding); - // ... compare other properties ... - - return result; - } - - bool compareElementLists(List expected, List actual) - { - //Assert.AreEqual(expected.Count, actual.Count); - if (expected.Count != actual.Count) - { - Debug.WriteLine($"MISMATCH : expected element count = {expected.Count}, actual = {actual.Count}"); - return false; - } - - bool result = true; - for (int i = 0; i < expected.Count; i++) - { - var exp = expected[i]; - var act = actual[i]; - var match = StringComparer.Ordinal.Equals(exp.Path, act.Path); - if (match) - { - result &= compareElements(exp, act); - } - else - { - Debug.WriteLine($"MISMATCH Path: expected='{exp.Path}', actual='{act.Path}'"); - result = false; - } - } - return result; - - } - - void TestTypeSlicingExample( - string id, - Action fixExpected = null, - Action fixInput = null) - { - var input = LoadInput(id); - Assert.IsNotNull(input); - fixInput?.Invoke(input); - - var expected = LoadExpected(id); - Assert.IsNotNull(expected); - Assert.IsTrue(expected.HasSnapshot); - fixExpected?.Invoke(expected); - var expectedElems = expected.Snapshot.Element; - - var expanded = _generator.Generate(input); - Assert.IsNotNull(expanded); - Assert.IsNull(_generator.Outcome); - - // TODO: Compare expanded & expected - var result = compareElementLists(expectedElems, expanded); - - // On failure, serialize to XML & dump - if (!result) - { - var clone = (StructureDefinition)input.DeepCopy(); - clone.Snapshot = new StructureDefinition.SnapshotComponent() { Element = expanded }; - var xml = serializer.SerializeToString(clone); - - var filePath = Path.Combine(Path.GetTempPath(), id + "_actual.xml"); - Debug.WriteLine($"FAILED: {id} {input.GetOrigin()}, save output to '{filePath}'"); - File.WriteAllText(filePath, xml); - //Debug.WriteLine(xml); - } - - Assert.IsTrue(result); - } - - // [WMR 20190807] TODO: Add code to fix invalid constraints in input/expected - - [TestMethod] public void TestTypeSlicingExample_t1() => TestTypeSlicingExample("t1"); - [TestMethod] public void TestTypeSlicingExample_t2() => TestTypeSlicingExample("t2"); - [TestMethod] public void TestTypeSlicingExample_t3() => TestTypeSlicingExample("t3"); - [TestMethod] public void TestTypeSlicingExample_t4() => TestTypeSlicingExample("t4"); - [TestMethod] public void TestTypeSlicingExample_t4a() => TestTypeSlicingExample("t4a"); - [TestMethod] public void TestTypeSlicingExample_t5() => TestTypeSlicingExample("t5"); - [TestMethod] public void TestTypeSlicingExample_t6() => TestTypeSlicingExample("t6"); - [TestMethod] public void TestTypeSlicingExample_t7() => TestTypeSlicingExample("t7"); - [TestMethod] public void TestTypeSlicingExample_t8() => TestTypeSlicingExample("t8"); - [TestMethod] public void TestTypeSlicingExample_t9() => TestTypeSlicingExample("t9"); - [TestMethod] public void TestTypeSlicingExample_t10() => TestTypeSlicingExample("t10"); - - [TestMethod] public void TestTypeSlicingExample_t11() => TestTypeSlicingExample("t11", FixExpected11_12); - - [TestMethod] public void TestTypeSlicingExample_t12() => TestTypeSlicingExample("t12", FixExpected11_12); - - [TestMethod] public void TestTypeSlicingExample_t12a() => TestTypeSlicingExample("t12a", FixExpected_12a); - [TestMethod] public void TestTypeSlicingExample_t14() => TestTypeSlicingExample("t14"); - [TestMethod] public void TestTypeSlicingExample_t15() => TestTypeSlicingExample("t15"); - [TestMethod] public void TestTypeSlicingExample_t16() => TestTypeSlicingExample("t16"); - [TestMethod] public void TestTypeSlicingExample_t17() => TestTypeSlicingExample("t17"); - [TestMethod] public void TestTypeSlicingExample_t18() => TestTypeSlicingExample("t18"); - [TestMethod] public void TestTypeSlicingExample_t19() => TestTypeSlicingExample("t19"); - //[TestMethod] public void TestTypeSlicingExample_t20() => TestTypeSlicingExample("t20"); - [TestMethod] public void TestTypeSlicingExample_t21() => TestTypeSlicingExample("t21"); - [TestMethod] public void TestTypeSlicingExample_t22() => TestTypeSlicingExample("t22"); - [TestMethod] public void TestTypeSlicingExample_t23() => TestTypeSlicingExample("t23"); - [TestMethod] public void TestTypeSlicingExample_t23a() => TestTypeSlicingExample("t23a"); - [TestMethod] public void TestTypeSlicingExample_t24a() => TestTypeSlicingExample("t24a"); - [TestMethod] public void TestTypeSlicingExample_t24b() => TestTypeSlicingExample("t24b"); - //[TestMethod] public void TestTypeSlicingExample_t25() => TestTypeSlicingExample("t25"); - [TestMethod] public void TestTypeSlicingExample_t26() => TestTypeSlicingExample("t26"); - [TestMethod] public void TestTypeSlicingExample_t27() => TestTypeSlicingExample("t27"); - [TestMethod] public void TestTypeSlicingExample_t28() => TestTypeSlicingExample("t28"); - [TestMethod] public void TestTypeSlicingExample_t29() => TestTypeSlicingExample("t29"); - //[TestMethod] public void TestTypeSlicingExample_t30a() => TestTypeSlicingExample("t30a"); - [TestMethod] public void TestTypeSlicingExample_t30b() => TestTypeSlicingExample("t30b"); - [TestMethod] public void TestTypeSlicingExample_t31() => TestTypeSlicingExample("t31"); - [TestMethod] public void TestTypeSlicingExample_t32() => TestTypeSlicingExample("t32"); - [TestMethod] public void TestTypeSlicingExample_t33() => TestTypeSlicingExample("t33"); - [TestMethod] public void TestTypeSlicingExample_t34() => TestTypeSlicingExample("t34"); - [TestMethod] public void TestTypeSlicingExample_t35() => TestTypeSlicingExample("t35"); - [TestMethod] public void TestTypeSlicingExample_t36() => TestTypeSlicingExample("t36"); - //[TestMethod] public void TestTypeSlicingExample_t37() => TestTypeSlicingExample("t37", null, FixInput37); - [TestMethod] public void TestTypeSlicingExample_t38() => TestTypeSlicingExample("t38"); - //[TestMethod] public void TestTypeSlicingExample_t39() => TestTypeSlicingExample("t39"); - [TestMethod] public void TestTypeSlicingExample_t40() => TestTypeSlicingExample("t40"); - [TestMethod] public void TestTypeSlicingExample_t41() => TestTypeSlicingExample("t41"); - [TestMethod] public void TestTypeSlicingExample_t42() => TestTypeSlicingExample("t42"); - [TestMethod] public void TestTypeSlicingExample_t43() => TestTypeSlicingExample("t43"); - [TestMethod] public void TestTypeSlicingExample_t44() => TestTypeSlicingExample("t44"); - [TestMethod] public void TestTypeSlicingExample_t45() => TestTypeSlicingExample("t45"); - [TestMethod] public void TestTypeSlicingExample_samply1() => TestTypeSlicingExample("samply1"); - //[TestMethod] public void TestTypeSlicingExample_au1() => TestTypeSlicingExample("au1"); - [TestMethod] public void TestTypeSlicingExample_au2() => TestTypeSlicingExample("au2"); - [TestMethod] public void TestTypeSlicingExample_au3() => TestTypeSlicingExample("au3"); - [TestMethod] public void TestTypeSlicingExample_dv1() => TestTypeSlicingExample("dv1"); - - // Private helpers - - void FixExpected11_12(StructureDefinition sd) - { - var elems = sd.Snapshot.Element; - var navDR = _DomainResourceNavigator; - - // FIX: Patient.extension inhers from DomainResource.extension - var elem = FindElementByPath(elems, "Patient.extension"); - FixExtensionBase(elem, navDR); - - // FIX: Patient.extension:name1 inherits from target extension definition "patient-birthTime" - elem = FindElementByPath(elems, "Patient.extension", "name1"); - elem.Max = "1"; - - // FIX: Patient.extension:name2 inherits from target extension definition "patient-mothersMaidenName" - elem = FindElementByPath(elems, "Patient.extension", "name2"); - elem.Max = "1"; - } - - void FixExpected_12a(StructureDefinition sd) - { - var elems = sd.Snapshot.Element; - var navDR = _DomainResourceNavigator; - - // FIX: Patient.extension inhers from DomainResource.extension - var elem = FindElementByPath(elems, "Patient.extension"); - FixExtensionBase(elem, navDR); - - // FIX: Patient.extension:name1 inherits from target extension definition "patient-birthTime" - elem = FindElementByPath(elems, "Patient.extension", "name1"); - elem.Max = "1"; - } - - [TestMethod] public void TestTypeSlicingExample13() => TestTypeSlicingExample("t13", FixExpected13); - - void FixExpected13(StructureDefinition sd) - { - var elems = sd.Snapshot.Element; - var navDR = _DomainResourceNavigator; - - // FIX: Patient.extension inhers from DomainResource.extension - var elem = FindElementByPath(elems, "Patient.extension"); - FixExtensionBase(elem, navDR); - - // FIX: Patient.extension:t inherits from target extension definition "patient-birthTime" - elem = FindElementByPath(elems, "Patient.extension", "t"); - elem.Max = "1"; - - // FIX: Patient.extension.extension inhers from DomainResource.extension - elem = FindElementById(elems, "Patient.extension:complex.extension:code.extension"); - FixExtensionBase(elem, navDR); - elem = FindElementById(elems, "Patient.extension:complex.extension:period.extension"); - FixExtensionBase(elem, navDR); - - // FIX: Patient.extension.extension.extension inhers from DomainResource.extension - elem = FindElementByPath(elems, "Patient.extension.extension.extension"); - FixExtensionBase(elem, navDR); - - // FIX: Add missing element: Patient.extension:complex.extension:code.value[x] - Assert.IsTrue(navDR.JumpToFirst("DomainResource.extension")); - elem = FindElementById(elems, "Patient.extension:complex.extension:code.url"); - var idx = elems.IndexOf(elem); - elem = new ElementDefinition(); - navDR.Current.CopyTo(elem); - elem.Path = "Patient.extension.extension.value[x]"; - elem.ElementId = "Patient.extension:complex.extension:code.value[x]"; - elems.Insert(idx + 1, elem); - - // FIX: Add missing element: Patient.extension:complex.extension:period.value[x] - elem = FindElementById(elems, "Patient.extension:complex.extension:period.url"); - idx = elems.IndexOf(elem); - elem = new ElementDefinition(); - navDR.Current.CopyTo(elem); - elem.Path = "Patient.extension.extension.value[x]"; - elem.ElementId = "Patient.extension:complex.extension:period.value[x]"; - elems.Insert(idx + 1, elem); - } - - void FixInput37(StructureDefinition sd) - { - var elems = sd.Differential.Element; - var navDR = _DomainResourceNavigator; - - // FIX: Fix typo in path - var elem = FindElementByPath(elems, "MedicationRequiest.dosageInstruction.timing.event"); - elem.Path = "MedicationRequest.dosageInstruction.timing.event"; - } - - void FixExtensionBase(ElementDefinition elem, ElementDefinitionNavigator navDR) - { - // FIX: extension element inhers from DomainResource.extension - Assert.IsTrue(navDR.JumpToFirst("DomainResource.extension")); - elem.Short = navDR.Current.Short; - elem.Definition = navDR.Current.Definition; - } - - static ElementDefinition FindElementById(List elems, string id) - { - var elem = elems.FirstOrDefault(e => e.ElementId == id); - Assert.IsNotNull(elem); - return elem; - } - - static ElementDefinition FindElementByPath(List elems, string path) - { - var elem = elems.FirstOrDefault(e => e.Path == path && e.SliceName is null); - Assert.IsNotNull(elem); - return elem; - } - - static ElementDefinition FindElementByPath(List elems, string path, string sliceName) - { - var elem = elems.FirstOrDefault(e => e.Path == path && e.SliceName == sliceName); - Assert.IsNotNull(elem); - return elem; - } - - - } - -} - -#endif \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs index e9449e3361..aeecf8b601 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs @@ -467,7 +467,7 @@ async Tasks.Task OpenStream(string filePath) var dupId = res.Id; var rootId = Guid.NewGuid().ToString(); res.Id = rootId; - _ = await new FhirXmlSerializer().SerializeToStringAsync(res); + _ = new FhirXmlSerializer().SerializeToString(res); var dupFilePath = Path.Combine(fullSubFolderPath, srcFile); Assert.IsTrue(File.Exists(dupFilePath)); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ConformanceSourceTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ConformanceSourceTests.cs index 5665167a2b..b6b12f37e8 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ConformanceSourceTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ConformanceSourceTests.cs @@ -445,12 +445,12 @@ public async Tasks.Task TestThreadSafety() } [TestMethod] - public async Tasks.Task TestRefreshAll() => await TestRefreshAsync(true); + public void TestRefreshAll() => testRefresh(true); [TestMethod] - public async Tasks.Task TestRefreshFile() => await TestRefreshAsync(false); + public void TestRefreshFile() => testRefresh(false); - async Tasks.Task TestRefreshAsync(bool refreshAll) + private static void testRefresh(bool refreshAll) { // Create a temporary folder with a single artifact file const string srcFileName = "TestPatient.xml"; @@ -524,7 +524,7 @@ void Refresh(params string[] files) Assert.AreEqual(patient.Id, "pat1"); patient.Id = "CHANGED"; var serializer = new FhirXmlSerializer(); - var xml = await serializer.SerializeToStringAsync(patient); + var xml = serializer.SerializeToString(patient); File.WriteAllText(tmpFilePath, xml); // Verify that Refresh updates the summary information @@ -546,17 +546,16 @@ void Refresh(params string[] files) } [TestMethod] - public async Tasks.Task TestParserSettings() + public void TestParserSettings() { // Create an invalid patient resource on disk var obs = new Observation() { Id = "1", - ReferenceRange = new List { - new Observation.ReferenceRangeComponent { Text = " "} } + ReferenceRange = [new Observation.ReferenceRangeComponent { Text = " " }] }; var nav = obs.ToTypedElement(); - var xml = await nav.ToXmlAsync(); + var xml = nav.ToXml(); var folderPath = Path.Combine(Path.GetTempPath(), "TestDirectorySource"); var filePath = Path.Combine(folderPath, "TestPatient.xml"); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs index 78a5a2d9b8..73f28a0acf 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs @@ -313,10 +313,9 @@ public void TestSetupIsOnce() Debug.WriteLine(String.Format("First time {0}, second time {1}", sw.ElapsedMilliseconds, sw2.ElapsedMilliseconds)); } - // [WMR 20160823] NEW - Verify FileDirectoryArtifactSource & ResolvingConflictException [TestMethod] - public async Tasks.Task TestCanonicalUrlConflicts() + public void TestCanonicalUrlConflicts() { //const string srcFileName = "extension-definitions.xml"; const string dupFileName = "patient-birthtime"; @@ -332,7 +331,7 @@ public async Tasks.Task TestCanonicalUrlConflicts() // Save back to disk to create a conflicting duplicate var b = new Bundle(); b.AddResourceEntry(ext, url); - var xml = await new FhirXmlSerializer().SerializeToStringAsync(b); + var xml = FhirXmlSerializer.Default.SerializeToString(b); var filePath = Path.Combine(DirectorySource.SpecificationDirectory, dupFileName) + ".xml"; var filePath2 = Path.Combine(DirectorySource.SpecificationDirectory, dupFileName) + "2.xml"; File.WriteAllText(filePath, xml); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs index d01454c1a4..d1bb180faa 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/StructureDefinitionWalkerTests.cs @@ -122,7 +122,7 @@ public async Tasks.Task WalkAcrossInlineExtension() nav.JumpToFirst("Patient.communication"); nav.MoveToNextSlice(); - var fortest = await nav.StructureDefinition.ToXmlAsync(); + var fortest = nav.StructureDefinition.ToXml(); var walker = new StructureDefinitionWalker(nav, _source); var elem = walker.Extension("http://hl7.org/fhir/StructureDefinition/patient-proficiency"); diff --git a/src/Hl7.Fhir.Support.Poco.Tests/Model/TypedElementToPocoTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/Model/TypedElementToPocoTests.cs index b6cd296967..f5f7c9443d 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/Model/TypedElementToPocoTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/Model/TypedElementToPocoTests.cs @@ -143,7 +143,7 @@ public void ParsesResourceWithOverflow() private T toPoco(T source) where T : Base, new() { - var poco = toPoco(source.ToElementNode()); + var poco = toPoco(source.ToPocoNode()); return poco.Should().BeOfType().Subject; } diff --git a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirJsonDeserializationTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirJsonDeserializationTests.cs index c43dab6734..b193e4c796 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirJsonDeserializationTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirJsonDeserializationTests.cs @@ -13,7 +13,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; using System.Text; using System.Text.Json; using COVE = Hl7.Fhir.Validation.CodedValidationException; @@ -102,7 +101,7 @@ public void TryDeserializePrimitiveValue(object input, object expectedResult, Ty } } - private static BaseFhirJsonPocoDeserializer getTestDeserializer(FhirJsonPocoDeserializerSettings settings) => + private static BaseFhirJsonPocoDeserializer getTestDeserializer(FhirJsonConverterOptions settings) => new(typeof(Patient).Assembly, settings); [TestMethod] @@ -264,7 +263,7 @@ PrimitiveType test() private static (Base?, IReadOnlyCollection) deserializeComplex(Type objectType, object testObject, out Utf8JsonReader readerState, - FhirJsonPocoDeserializerSettings settings) + FhirJsonConverterOptions settings) { // For the tests, enable full XHML validation so we can test it when necessary. var deserializer = new BaseFhirJsonPocoDeserializer(typeof(Patient).Assembly, settings); @@ -692,7 +691,7 @@ public void TestDisableBase64Parsing() attachment = deserializeAttachment(new() { DisableBase64Decoding = true }); attachment.DataElement.ObjectValue.Should().BeOfType().And.Subject.Should().Be("SGkh"); - static Attachment deserializeAttachment(FhirJsonPocoDeserializerSettings settings) + static Attachment deserializeAttachment(FhirJsonConverterOptions settings) { var (attachment, errors) = deserializeComplex(typeof(Attachment), new { data = "SGkh" }, out _, settings); diff --git a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs index 5a39ae7433..0b85345b4c 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs @@ -516,8 +516,7 @@ public void TestNewXmlParserNarrativeParsing() } }; - var serializer = new BaseFhirXmlPocoSerializer(Specification.FhirRelease.STU3); - var actual = serializer.SerializeToString(patient); + var actual = FhirXmlSerializer.Default.SerializeToString(patient); // now parse this back out with the new parser BaseFhirXmlPocoDeserializer ds = getTestDeserializer(new FhirXmlPocoDeserializerSettings()); diff --git a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlSerializationTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlSerializationTests.cs index 4841fda564..cc3a75551e 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlSerializationTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlSerializationTests.cs @@ -17,7 +17,7 @@ namespace Hl7.Fhir.Support.Poco.Tests [TestClass] public class FhirXmlSerializationTests { - private (Patient, string) getEdgecases() + private static (Patient, string) getEdgecases() { var filename = Path.Combine("TestData", "fp-test-patient.xml"); var expected = File.ReadAllText(filename); @@ -32,8 +32,8 @@ public void CanSerializeEdgeCases() { var (poco, expected) = getEdgecases(); - var serializer = new BaseFhirXmlPocoSerializer(Specification.FhirRelease.STU3); - var actual = SerializationUtil.WriteXmlToString(poco, (o, w) => serializer.Serialize(o, w)); + var serializer = new BaseFhirXmlSerializer(ModelInfo.ModelInspector); + var actual = SerializationUtil.WriteXmlToString(w => serializer.Serialize(poco, w)); XmlAssert.AreSame("edgecases", expected, actual, ignoreSchemaLocation: true); } @@ -41,16 +41,30 @@ public void CanSerializeEdgeCases() [TestMethod] public void SerializesInvalidData() { - var serializer = new BaseFhirXmlPocoSerializer(Specification.FhirRelease.STU3); + var serializer = new BaseFhirXmlSerializer(ModelInfo.ModelInspector); FhirBoolean b = new() { ObjectValue = "treu" }; - var xdoc = XDocument.Parse(SerializationUtil.WriteXmlToString(b, (o, w) => serializer.Serialize(o, w))); + var xdoc = XDocument.Parse(SerializationUtil.WriteXmlToString(w => serializer.Serialize(b, w))); Assert.AreEqual("treu", xdoc.Root.Attribute(XName.Get("value")).Value); Patient p = new() { Contact = new() { new Patient.ContactComponent() } }; - xdoc = XDocument.Parse(SerializationUtil.WriteXmlToString(p, (o, w) => serializer.Serialize(o, w))); + xdoc = XDocument.Parse(SerializationUtil.WriteXmlToString(w => serializer.Serialize(p, w))); var contactArray = xdoc.Root.Elements(XName.Get("contact", XmlNs.FHIR)); contactArray.Count().Should().Be(1); contactArray.First().Elements().Should().BeEmpty(); } + + [TestMethod] + public void SerializesSubtree() + { + var serializer = new BaseFhirXmlSerializer(ModelInfo.ModelInspector); + FhirBoolean b = new() { ObjectValue = "treu" }; + + serializer.SerializeToString(b).Should().StartWith("(ModelInspector.ForType()); var options = new JsonSerializerOptions() - .ForFhir(typeof(Patient).Assembly, new FhirJsonPocoSerializerSettings { SummaryFilter = filter }) + .ForFhir(typeof(Patient).Assembly, new FhirJsonConverterOptions { SummaryFilter = filter }) .Pretty(); string summarizedJson = JsonSerializer.Serialize(full, options); @@ -176,6 +176,7 @@ private static IEnumerable> traverse(Base x) static IEnumerable> childrenAndMe(KeyValuePair y) => (y.Value switch { + Meta m => [], // skip Meta, since we've added SUBSETTED tags, so don't count those ICollection array => array.Cast().SelectMany(bsi => childrenAndMe(KeyValuePair.Create(y.Key, bsi))), Base obj => obj.EnumerateElements().SelectMany(childrenAndMe), _ => [] diff --git a/src/Hl7.Fhir.Support.Tests/Hl7.Fhir.Support.Tests.csproj b/src/Hl7.Fhir.Support.Tests/Hl7.Fhir.Support.Tests.csproj index 2ad432048c..ef46d867f9 100644 --- a/src/Hl7.Fhir.Support.Tests/Hl7.Fhir.Support.Tests.csproj +++ b/src/Hl7.Fhir.Support.Tests/Hl7.Fhir.Support.Tests.csproj @@ -8,7 +8,6 @@ - @@ -17,4 +16,4 @@ - \ No newline at end of file + diff --git a/src/Hl7.Fhir.Support.Tests/JsonAssert.cs b/src/Hl7.Fhir.Support.Tests/JsonAssert.cs index 5be96a748e..88cfa5159e 100644 --- a/src/Hl7.Fhir.Support.Tests/JsonAssert.cs +++ b/src/Hl7.Fhir.Support.Tests/JsonAssert.cs @@ -7,6 +7,7 @@ */ using Hl7.Fhir.Utility; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -17,10 +18,24 @@ namespace Hl7.Fhir.Tests { public class JsonAssert { + private static JObject jObjectFromJsonText(string json) + { + using var reader = SerializationUtil.JsonReaderFromJsonText(json); + return SerializationUtil.JObjectFromReader(reader); + } + + public static void AreSame(string filename, string expected, string actual) + { + var errors = new List(); + AreSame(filename, expected, actual, errors); + if(errors.Any()) + Assert.Fail(string.Join("\r\n", errors)); + } + public static void AreSame(string filename, string expected, string actual, List errors) { - var exp = SerializationUtil.JObjectFromJsonText(expected); - var act = SerializationUtil.JObjectFromJsonText(actual); + var exp = jObjectFromJsonText(expected); + var act = jObjectFromJsonText(actual); AreSame(filename, exp, act, errors); } @@ -187,4 +202,4 @@ public static void compareValues(string filename, object exp, object act, string } } } -} +} \ No newline at end of file diff --git a/src/Hl7.FhirPath.R4.Tests/Hl7.FhirPath.R4.Tests.csproj b/src/Hl7.FhirPath.R4.Tests/Hl7.FhirPath.R4.Tests.csproj index 8481eb7517..2c9c977e02 100644 --- a/src/Hl7.FhirPath.R4.Tests/Hl7.FhirPath.R4.Tests.csproj +++ b/src/Hl7.FhirPath.R4.Tests/Hl7.FhirPath.R4.Tests.csproj @@ -21,9 +21,8 @@ - - \ No newline at end of file + diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs index dbc9060087..4f08ef6409 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTest.cs @@ -605,7 +605,7 @@ public void defineVariable_with_compile_success() var expr = "defineVariable('root', 'r1-').select(defineVariable('v1', 'v1').defineVariable('v2', 'v2').select(%v1 | %v2)).select(%root & $this)"; var compiler = new FhirPathCompiler(); var exprCompiled = compiler.Compile(expr); - var r = exprCompiled(fixture.PatientExample.ToElementNode(), new FhirEvaluationContext()); + var r = exprCompiled(fixture.PatientExample.ToPocoNode(), new FhirEvaluationContext()); Assert.AreEqual(2, r.Count()); Assert.AreEqual("r1-v1", r.First().Value); Assert.AreEqual("r1-v2", r.Skip(1).First().Value); diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs index f0e0e70a3d..f33a463760 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathNavTest.cs @@ -28,7 +28,7 @@ public IScopedNode getTestData() { var tpXml = TestData.ReadTextFile("fp-test-patient.xml"); var engine = FhirSerializationEngineFactory.Ostrich(ModelInfo.ModelInspector); - return engine.DeserializeFromXml(tpXml).ToElementNode(); + return engine.DeserializeFromXml(tpXml).ToPocoNode(); } [Fact] diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs index 0684c29625..d90f344cd4 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathParallelTest.cs @@ -53,7 +53,7 @@ public static async Tasks.Task MassiveParallelSelectsShouldBeCorrect(string test var processor = new ActionBlock(r => { var evalContext = new EvaluationContext(); - var canonical = selector(r.ToElementNode(), "url", evalContext).Single().Value.ToString(); + var canonical = selector(r.ToPocoNode(), "url", evalContext).Single().Value.ToString(); actual.Add((canonical, r)); } , diff --git a/src/Hl7.FhirPath.Tests/HL7.FhirPath.Tests.csproj b/src/Hl7.FhirPath.Tests/HL7.FhirPath.Tests.csproj index 2c0d93b49f..81c354ed9b 100644 --- a/src/Hl7.FhirPath.Tests/HL7.FhirPath.Tests.csproj +++ b/src/Hl7.FhirPath.Tests/HL7.FhirPath.Tests.csproj @@ -9,7 +9,6 @@ - @@ -18,4 +17,4 @@ - \ No newline at end of file + diff --git a/src/firely-net-sdk-tests.props b/src/firely-net-sdk-tests.props index aa55bcfd51..e38eed3404 100644 --- a/src/firely-net-sdk-tests.props +++ b/src/firely-net-sdk-tests.props @@ -3,11 +3,12 @@ net8.0 12.0 true + true true - 1591, 0618, SDK0001 + 1591, SDK0001 @@ -20,7 +21,6 @@ - @@ -39,6 +39,6 @@ ..\FhirNetApi.publickey False - 1591, 0618, SDK0001 + 1591, SDK0001 diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index 08ffc0c260..a397536082 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -22,12 +22,6 @@ true - - - CS4014 - - @@ -42,9 +36,12 @@ 12.0 True Debug;Release;FullDebug - true 5.11.1 + + CS4014 + true