Skip to content
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

2610 assert unique json ids #2680

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ private void deserializeObjectInto<T>(
while (reader.TokenType != JsonTokenType.EndObject)
{
var currentPropertyName = reader.GetString()!;
if (!objectParsingState.TryAddToEncounteredKeys(currentPropertyName))
{
state.Errors.Add(ERR.DUPLICATE_KEY(ref reader, state.Path.GetInstancePath(), currentPropertyName));
}

// The resourceType property on the level of a resource is used to determine
// the type and should otherwise be skipped when processing a resource.
Expand Down Expand Up @@ -383,7 +387,7 @@ FhirJsonPocoDeserializerState state

// If this is a FhirPrimitive, make sure we delay validation until we had the
// chance to encounter both the `name` and `_name` property.
if (delayedValidations is not null && propertyValueMapping.IsFhirPrimitive)
if (propertyValueMapping.IsFhirPrimitive)
{
delayedValidations.ScheduleDelayedValidation(
propertyMapping.Name + PROPERTY_VALIDATION_KEY_SUFFIX,
Expand Down Expand Up @@ -458,11 +462,12 @@ internal class ObjectParsingState
{
private readonly Dictionary<string, Action> _validations = new();
private readonly Dictionary<string, int> _parsedPropValue = new();
private readonly HashSet<string> _encounteredIDs = new();

public int GetPropertyIndex(string memberName)
{
if (_parsedPropValue.ContainsKey(memberName))
return _parsedPropValue[memberName];
if (_parsedPropValue.TryGetValue(memberName, out int index))
return index;
_parsedPropValue.Add(memberName, 0);
return 0;
}
Expand All @@ -472,6 +477,11 @@ public void SetPropertyIndex(string memberName, int count)
_parsedPropValue[memberName] = count;
}

public bool TryAddToEncounteredKeys(string key)
{
return _encounteredIDs.Add(key);
}

public void ScheduleDelayedValidation(string key, Action validation)
{
// Add or overwrite the entry for the given key.
Expand Down Expand Up @@ -926,7 +936,7 @@ private static (PropertyMapping? propMapping, ClassMapping? propValueMapping, Fh
(ClassMapping? propertyValueMapping, FhirJsonException? error) = propertyMapping.Choice switch
{
ChoiceType.None or ChoiceType.ResourceChoice =>
inspector.FindOrImportClassMapping(propertyMapping.GetInstantiableType()) is ClassMapping m
inspector.FindOrImportClassMapping(propertyMapping.GetInstantiableType()) is { } m
? (m, null)
: throw new InvalidOperationException($"Encountered property type {propertyMapping.ImplementingType} for which no mapping was found in the model assemblies. " + reader.GenerateLocationMessage()),
ChoiceType.DatatypeChoice => getChoiceClassMapping(ref reader),
Expand All @@ -941,7 +951,7 @@ private static (PropertyMapping? propMapping, ClassMapping? propValueMapping, Fh

return string.IsNullOrEmpty(typeSuffix)
? (null, ERR.CHOICE_ELEMENT_HAS_NO_TYPE(ref r, path.GetInstancePath(), propertyMapping.Name))
: inspector.FindClassMapping(typeSuffix) is ClassMapping cm
: inspector.FindClassMapping(typeSuffix) is { } cm
? (cm, null)
: (default, ERR.CHOICE_ELEMENT_HAS_UNKOWN_TYPE(ref r, path.GetInstancePath(), propertyMapping.Name, typeSuffix));
}
Expand Down
2 changes: 2 additions & 0 deletions src/Hl7.Fhir.Base/Serialization/FhirJsonException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class FhirJsonException : ExtendedCodedException
public const string PROPERTY_MAY_NOT_BE_EMPTY_CODE = "JSON127";

public const string DUPLICATE_ARRAY_CODE = "JSON128";
public const string DUPLICATE_KEYS_CODE = "JSON129";

// ==========================================
// Unrecoverable Errors
Expand All @@ -76,6 +77,7 @@ public class FhirJsonException : ExtendedCodedException
internal static FhirJsonException RESOURCE_TYPE_NOT_A_RESOURCE(ref Utf8JsonReader reader, string instancePath, string name) => Initialize(ref reader, instancePath, RESOURCE_TYPE_NOT_A_RESOURCE_CODE, $"Data type '{name}' in property 'resourceType' is not a type of resource.", OO_Sev.Fatal, OO_Typ.Structure);
internal static FhirJsonException UNKNOWN_PROPERTY_FOUND(ref Utf8JsonReader reader, string instancePath, string propName) => Initialize(ref reader, instancePath, UNKNOWN_PROPERTY_FOUND_CODE, $"Encountered unrecognized element '{propName}'.", OO_Sev.Error, OO_Typ.Structure); // this could be ignored, so isn't fatal?
internal static FhirJsonException INCOMPATIBLE_SIMPLE_VALUE(ref Utf8JsonReader reader, string instancePath, string value, FhirJsonException? err) => Initialize(ref reader, instancePath, INCOMPATIBLE_SIMPLE_VALUE_CODE, $"Json primitive value does not match the expected type of the primitive property. Details: ({value})", OO_Sev.Fatal, OO_Typ.Value, err);
internal static FhirJsonException DUPLICATE_KEY(ref Utf8JsonReader reader, string instancePath, string key) => Initialize(ref reader, instancePath, DUPLICATE_KEYS_CODE, $"Encountered duplicate key \"{key}\"", OO_Sev.Fatal, OO_Typ.Structure);

// ==========================================
// Recoverable Errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,36 @@ public void TestParseObjectPublicMethod()
// ok!
}
}

[TestMethod]
public void TestDuplicateIDs()
{
string jsonErrInput = "{\n" +
"\"resourceType\" : \"Patient\",\n" +
"\"active\" : true,\n" +
"\"active\" : false\n" +
"}";

var options = new JsonSerializerOptions().ForFhir(typeof(TestPatient).Assembly);

try
{
var actual = JsonSerializer.Deserialize<TestPatient>(jsonErrInput, options);
Assert.Fail("Should have encountered errors.");
}
catch (DeserializationFailedException dfe)
{
Console.WriteLine(dfe.Message);
var recoveredActual = JsonSerializer.Serialize(dfe.PartialResult, options);
Console.WriteLine(recoveredActual);

assertErrors(dfe.Exceptions, new string[]
{
ERR.DUPLICATE_KEYS_CODE,
});
}

}

[TestMethod]
public void TestRecovery()
Expand Down
Loading