From 31a792a8cbcafaf3625134181ef1619c3253cbde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Wed, 30 Apr 2025 15:23:30 +0200 Subject: [PATCH 1/2] Annotate in sdk5 --- .../BaseFhirJsonPocoDeserializer.cs | 14 ++- .../BaseFhirXmlPocoDeserializer.cs | 14 ++- .../FhirJsonPocoDeserializerSettings.cs | 9 ++ .../FhirXmlPocoDeserializerSettings.cs | 9 ++ ....Fhir.Serialization.Shared.Tests.projitems | 1 + .../ParseDemoPatientLineInfoAnnotationPoco.cs | 88 +++++++++++++++++++ 6 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs index 5719240418..c569cd0531 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs @@ -344,7 +344,7 @@ FhirJsonPocoDeserializerState state { object? result; var oldErrorCount = state.Errors.Count; - var (line, pos) = reader.CurrentState.GetLocation(); + var (line, pos) = reader.GetLocation(); // There might be an existing value, since FhirPrimitives may be spread out over two properties // (one with, and one without the '_') @@ -392,6 +392,9 @@ FhirJsonPocoDeserializerState state } } + if (Settings.AnnotateLineInfo && result is Base b) + b.AddAnnotation(new JsonSerializationDetails { LineNumber = (int)line, LinePosition = (int)pos }); + // Only do validation when no parse errors were encountered, otherwise we'll just // produce spurious messages. if (Settings.Validator is not null && (Settings.ValidateOnFailedParse || oldErrorCount == state.Errors.Count)) @@ -456,7 +459,12 @@ FhirJsonPocoDeserializerState state // to simply create a list by Adding(). Not the fastest approach :-( while (reader.TokenType != JsonTokenType.EndArray) { + var (line, pos) = reader.GetLocation(); + var result = deserializeSingleValue(ref reader, propertyValueMapping, propertyMapping, state); + if (Settings.AnnotateLineInfo && result is Base b) + b.AddAnnotation(new JsonSerializationDetails { LineNumber = (int)line, LinePosition = (int)pos, ArrayIndex = listInstance.Count }); + listInstance.Add(result); state.Path.IncrementIndex(); @@ -548,6 +556,7 @@ FhirJsonPocoDeserializerState state while (reader.TokenType != JsonTokenType.EndArray) { + var (line, pos) = reader.GetLocation(); if (elementIndex >= originalSize) existingList.Add(null); @@ -564,6 +573,9 @@ FhirJsonPocoDeserializerState state onlyNulls = false; _ = DeserializeFhirPrimitive((PrimitiveType)existingList[elementIndex]!, propertyName, propertyValueMapping, fhirType, ref reader, delayedValidations, state); + if (Settings.AnnotateLineInfo && existingList[elementIndex] is Base b) + b.AddAnnotation(new JsonSerializationDetails { LineNumber = (int)line, LinePosition = (int)pos, ArrayIndex = elementIndex }); + delayedValidations.SetPropertyIndex(propertyName, existingList.Count); } diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs index 57a48c7e3d..ed4147c915 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs @@ -220,6 +220,9 @@ private void deserializeElementInto(Base target, ClassMapping mapping, XmlReader var hasValueAttribute = reader.GetAttribute("value") != null; var depth = reader.Depth; var name = reader.LocalName; + + if (Settings.AnnotateLineInfo) + target.AddAnnotation(new XmlSerializationDetails { LineNumber = lineNumber, LinePosition = position }); //check if on opening tag if (reader.NodeType != XmlNodeType.Element) @@ -333,7 +336,7 @@ private void deserializePropertyValue(Base target, XmlReader reader, FhirXmlPoco { state.Errors.Add(ERR.INVALID_DUPLICATE_PROPERTY(reader, state.Path.GetInstancePath(), propMapping.Name)); } - + if (Settings.Validator is not null && (Settings.ValidateOnFailedParse || oldErrors == state.Errors.Count)) { var context = new PropertyDeserializationContext( @@ -344,6 +347,9 @@ private void deserializePropertyValue(Base target, XmlReader reader, FhirXmlPoco PocoDeserializationHelper.RunPropertyValidation(ref result, Settings.Validator, context, state.Errors); } + + if (Settings.AnnotateLineInfo && result is Base b) + b.AddAnnotation(new XmlSerializationDetails { LineNumber = lineNumber, LinePosition = position }); propMapping.SetValue(target, result); } @@ -513,9 +519,10 @@ private void readAttribute(Base target, PropertyMapping propMapping, string elem if (parsedValue != null) { + var (lineNumber, position) = reader.GenerateLineInfo(); + if (Settings.Validator is not null && (Settings.ValidateOnFailedParse || oldErrors == state.Errors.Count)) { - var (lineNumber, position) = reader.GenerateLineInfo(); var name = reader.LocalName; var context = new PropertyDeserializationContext( @@ -530,6 +537,9 @@ private void readAttribute(Base target, PropertyMapping propMapping, string elem if (target is PrimitiveType primitive && propMapping.Name == "value") { primitive.ObjectValue = parsedValue; + + if (Settings.AnnotateLineInfo) + target.AddAnnotation(new XmlSerializationDetails { LineNumber = lineNumber, LinePosition = position }); } else { diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoDeserializerSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoDeserializerSettings.cs index ac1179b3b8..6f813cfb76 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoDeserializerSettings.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonPocoDeserializerSettings.cs @@ -62,6 +62,15 @@ public record FhirJsonPocoDeserializerSettings /// resource was clean and possibly ok to process). /// public bool AnnotateResourceParseExceptions { get; init; } = false; + + /// + /// Enable annotating line information of the parsed resources and properties. + /// + /// + /// This has a big impact on memory usage, as every element has to be aware where it was in the source data. + /// It is recommended to be kept disabled. + /// + public bool AnnotateLineInfo { get; init; } = false; } /// diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs index b854965e61..fc99a1df5c 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs @@ -39,6 +39,15 @@ public class FhirXmlPocoDeserializerSettings /// resource was clean and possibly ok to process). /// public bool AnnotateResourceParseExceptions { get; init; } = false; + + /// + /// Enable annotating line information of the parsed resources and properties. + /// + /// + /// This has a big impact on memory usage, as every element has to be aware where it was in the source data. + /// It is recommended to be kept disabled. + /// + public bool AnnotateLineInfo { get; init; } = false; } } diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems b/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems index 6214e167c6..8b85e57869 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems @@ -13,6 +13,7 @@ + diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs new file mode 100644 index 0000000000..6f864f8380 --- /dev/null +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs @@ -0,0 +1,88 @@ +using FluentAssertions; +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections; +using System.IO; + +namespace Hl7.Fhir.Serialization.Tests +{ + [TestClass] + public class ParseDemoPatientLineInfoAnnotationPoco + { + private T getXmlPocoAnnotated(string xml) where T : Resource + { + try + { + return (T)new FhirXmlPocoDeserializer(new FhirXmlPocoDeserializerSettings(){ AnnotateLineInfo = true }).DeserializeResource(xml); + } + catch (DeserializationFailedException e) + { + return (T)e.PartialResult; + } + } + + private T getJsonPocoAnnotated(string json) where T : Resource + { + try + { + + return (T)new FhirJsonPocoDeserializer(new FhirJsonPocoDeserializerSettings() { AnnotateLineInfo = true }).DeserializeResource(json); + } + catch (DeserializationFailedException e) + { + return (T)e.PartialResult; + } + } + + [TestMethod] + public void HasLineNumbers_PocoFromXml() + { + var xml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); + var nav = getXmlPocoAnnotated(xml); + + foreach (var c in nav.Children) + { + CheckAllElementsAnnotated(c); + } + } + + [TestMethod] + public void HasLineNumbers_PocoFromJson() + { + var json = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.json")); + var nav = getJsonPocoAnnotated(json); + + foreach (var c in nav.Children) + { + CheckAllElementsAnnotated(c); + } + } + + public void CheckAllElementsAnnotated(object element) where T : IPositionInfo + { + Assert.IsNotNull(element); + if (element is Base baseElement) + { + var posInfo = baseElement.Annotation(); + + posInfo.Should().NotBeNull(); + posInfo.LineNumber.Should().NotBe(-1).And.NotBe(0); + posInfo.LinePosition.Should().NotBe(-1).And.NotBe(0); + + foreach (var (_, baseChild) in baseElement) + { + CheckAllElementsAnnotated(baseChild); + } + } + + if (element is IList list) + { + foreach (var listElement in list) + { + CheckAllElementsAnnotated(listElement); + } + } + } + } +} \ No newline at end of file From 758623cd658f0c58db870a1713e81638a2c0244e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Thu, 1 May 2025 16:23:08 +0200 Subject: [PATCH 2/2] Feedback changes --- .../ParseDemoPatientLineInfoAnnotationPoco.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs index 6f864f8380..83c515928c 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/ParseDemoPatientLineInfoAnnotationPoco.cs @@ -10,29 +10,16 @@ namespace Hl7.Fhir.Serialization.Tests [TestClass] public class ParseDemoPatientLineInfoAnnotationPoco { - private T getXmlPocoAnnotated(string xml) where T : Resource + private static T getXmlPocoAnnotated(string xml) where T : Resource { - try - { - return (T)new FhirXmlPocoDeserializer(new FhirXmlPocoDeserializerSettings(){ AnnotateLineInfo = true }).DeserializeResource(xml); - } - catch (DeserializationFailedException e) - { - return (T)e.PartialResult; - } + new FhirXmlPocoDeserializer(new FhirXmlPocoDeserializerSettings(){ AnnotateLineInfo = true }).TryDeserializeResource(xml, out var resource, out _); + return (T)resource; } - private T getJsonPocoAnnotated(string json) where T : Resource + private static T getJsonPocoAnnotated(string json) where T : Resource { - try - { - - return (T)new FhirJsonPocoDeserializer(new FhirJsonPocoDeserializerSettings() { AnnotateLineInfo = true }).DeserializeResource(json); - } - catch (DeserializationFailedException e) - { - return (T)e.PartialResult; - } + new FhirJsonPocoDeserializer(new FhirJsonPocoDeserializerSettings(){ AnnotateLineInfo = true }).TryDeserializeResource(json, out var resource, out _); + return (T)resource; } [TestMethod]