-
Notifications
You must be signed in to change notification settings - Fork 357
Fallback to dynamic/guessed types on unknown data #3080
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fallback to dynamic/guessed types on unknown data #3080
Conversation
| if(choiceMapping is not null) | ||
| return (choiceMapping, null); | ||
|
|
||
| return (null, ERR.CHOICE_ELEMENT_HAS_UNKOWN_TYPE(ref r, path.GetInstancePath(), propertyMapping.Name, typeSuffix)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should never happen. In fact, the NewPocoBuilder has constants for the classmappings for DynamicDataType, and this will never be null when we use those.
| FhirJsonPocoDeserializerState state, | ||
| bool stayOnLastToken = false) where T : Base | ||
| { | ||
| if (reader.TokenType != JsonTokenType.StartObject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be more flexible in our thinking: we will only call this function IF we are at an object. I know at this moment we are doing it if we are expecting an object (because of classmappings etc), but since those are just hints now, we should probably never end up in this function if we don't actually see an object on the wire.
| } | ||
| } | ||
|
|
||
| private object? deserializeUnexpectedJsonValue(string propertyName, ref Utf8JsonReader reader, FhirJsonPocoDeserializerState state, bool stayOnLastToken, ClassMapping? propertySuggestion = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I think this is going to be the main workhorse, in fact we could call this straight from the main TryDeserializeResouirce and TryDeserializeObject functions - we're only expecting something, but we're no longer bound to it, so this dynamic function is really nice.
| // There might be an existing value, since FhirPrimitives may be spread out over two properties | ||
| // (one with, and one without the '_') | ||
| var existingValue = propertyMapping.GetValue(target); | ||
| target.TryGetValue(propertyMapping.Name, out var existingValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we've moved from using IL (in GetValue) to our generated function now, which might be a bit slower.
…into feature/parsers-overflow-unknown-data # Conflicts: # src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs
| { | ||
| state.Path.EnterElement(name, null, choiceType.IsPrimitive); | ||
|
|
||
| result = DeserializeFhirPrimitive(value as PrimitiveType, name, choiceType, null, ref reader, new(), state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not need to be a primitive, right? Choice types can be non-primitives too,
| { | ||
| state.Path.EnterElement(name, null, choiceType.IsPrimitive); | ||
|
|
||
| result = DeserializeFhirPrimitive(value as PrimitiveType, name, choiceType, null, ref reader, new(), state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if it is a list?
| { | ||
| state.Path.EnterElement(propMapping!.Name, !propMapping.IsCollection ? null : 0, propMapping.IsPrimitive); | ||
| state.Path.EnterElement(propMapping!.Name, isEnteringJsonArray(ref reader) ? 0 : null, propMapping.IsPrimitive); | ||
| deserializePropertyValueInto(target, currentPropertyName, propMapping, propValueMapping!, ref reader, objectParsingState, state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should try to merge deserializeUnknownPropertiesInto and deserializePropertyValueInto into a single function? (it needs to do the same thing, with "the only difference" that we don't have type information)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe by making sure we always have metadata, but then the metadata for DynamicPrimitive/DynamicType/DynaimcResource etc.
| writer.WritePropertyName(elementName); | ||
| SerializePrimitiveValue(value.ObjectValue, writer, requiredType); | ||
| } | ||
| // Since the object is not null, try overflow, but only if no extension data defined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont really understand whats going on, but it looks like we're serializing the contents of the overflow into the primitive element. This is invalid fhir json, so I'd like to understand better which problem we are solving here. It seems the overflow should be put under the _element?
| if(!string.IsNullOrEmpty(typeSuffix)) | ||
| choiceMapping = inspector.FindClassMapping(typeSuffix); | ||
|
|
||
| choiceMapping ??= inspector.FindClassMapping(nameof(DynamicDataType)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should introduce a non-null ClassMapping.DynamicDataType property (and use that in the NewPocoBuilder too), so the last line of this function (which will and should never be reached) can be removed.
| if(!string.IsNullOrEmpty(typeSuffix)) | ||
| choiceMapping = inspector.FindClassMapping(typeSuffix); | ||
|
|
||
| choiceMapping ??= inspector.FindClassMapping(nameof(DynamicDataType)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should introduce a non-null ClassMapping.DynamicDataType property (and use that in the NewPocoBuilder too), so the last line of this function (which will and should never be reached) can be removed.
| } | ||
| else if (reader.TokenType == JsonTokenType.StartArray) | ||
| { | ||
| var peeked = peekTypeNested(ref reader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to avoid these peeks (since they do cost a bit of time) for the case where we do have a mapping?
| [ | ||
| new { resourceType = nameof(OperationOutcome), crap = 5 }, JsonTokenType.EndObject, | ||
| ERR.UNKNOWN_PROPERTY_FOUND_CODE | ||
| COVE.INCORRECT_CARDINALITY_MIN_CODE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be different again when we integrate Kas's changes.
| new { name = "Ewout", _telecom = new object[] { new { system = "phone" }, new { systemX = "b" } } }, | ||
| checkData, ERR.USE_OF_UNDERSCORE_ILLEGAL_CODE, ERR.UNKNOWN_PROPERTY_FOUND_CODE); | ||
| yield return data<ContactDetail>(new { name = new[] { "Ewout" } }, ERR.EXPECTED_PRIMITIVE_NOT_ARRAY_CODE); | ||
| checkData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this use of underscore illegal gone?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!
| Console.WriteLine(recoveredActual); | ||
|
|
||
| assertErrors(dfe.Exceptions, [ | ||
| COVE.LITERAL_INVALID_CODE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should review this test after Kas's work has been merged, then we can verify the original tests versus the completed one with both your PRs
| [DataRow(PERMISSIVEXML, null, POCO_EMPTY_VALUE, POCO_EMPTY_VALUE, DisplayName = "Permissive XML")] | ||
| [DataRow(BWCOMPATIBLEXML, POCO_UNKNOWN_ELEMENT, POCO_UNKNOWN_ELEMENT, null, DisplayName = "Backwards-compatible XML")] | ||
| [DataRow(POCO_WRONGXML, POCO_EXPECTED_OBJECT, POCO_EXPECTED_OBJECT, POCO_EXPECTED_OBJECT, DisplayName = "Wrong XML")] | ||
| [DataRow(BWCOMPATIBLEXML, null, null, null, DisplayName = "Backwards-compatible XML")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't testing much anymore, it was supposed to test the differences between CORREECt/permissive/bw compat etc, but it no longer functions as a good test, we will need to write another one. I will take this up as part of #2878
| @@ -1,16 +1,87 @@ | |||
| { | |||
| "resourceType": "Patient", | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should manually review the new output (recovered + dumped errors) to verify this after pulling the PR.
ewoutkramer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're working on this already....
|
Superceded by #3094. |
Description
Targets poco branch for ease of viewing just the relevant changes, and will require the other pr.
Related issues
Closes #3050