Component:
Tableau.Migration.JsonConverters — SerializableContentLocation.VerifyDeseralization()
SDK Version:
6.0.0
Reproduction steps:
- Have a Tableau Server source with a content item (workbook or datasource) whose name contains a literal / character (e.g. a workbook named "ItemWith/SlashInName" inside project "Top-Level").
- Create a mapping function that maps the item to a new top-level project using
ContentLocation.create(path_separator, path_segments)
- Run a migration. The SDK serializes and saves manifest.json successfully.
- On the next run, pass the saved manifest to MigrationManifestSerializer.LoadAsync() to resume the migration.
Expected behavior:
The manifest loads successfully.
Actual behavior:
A MismatchException is thrown:
Tableau.Migration.JsonConverters.Exceptions.MismatchException:
Path should be Top-Level/ItemWith/SlashInName
but is Top-Level/ItemWith\/SlashInName.
at Tableau.Migration.JsonConverters.SerializableObjects.SerializableContentLocation.VerifyDeseralization()
at Tableau.Migration.JsonConverters.SerializableObjects.SerializableContentLocation.AsContentLocation()
at Tableau.Migration.JsonConverters.SerializableObjects.SerializableContentReference.AsContentReferenceStub()
at Tableau.Migration.JsonConverters.SerializableObjects.SerializableManifestEntry...get_Source()
Root cause analysis:
The manifest JSON serializer writes the / character inside item names as / — an optional but valid JSON escape sequence per the JSON specification (RFC 8259 §7). However, VerifyDeseralization() appears to reconstruct the expected path string from the content location segments (using unescaped /) and then compares it as a raw string against the value deserialized from JSON. Because the JSON parser returns the literal two-character sequence / rather than normalizing it to / before the comparison, the strings differ and the exception is thrown.
This is a round-trip bug: the serializer produces JSON the deserializer cannot accept, on the same machine, with no external changes to the manifest file.
Workaround:
Manually post-process manifest.json after each save, replacing all / occurrences with /. Since these are equivalent JSON representations of the same character, this is semantically safe but should not be required.
Component:
Tableau.Migration.JsonConverters — SerializableContentLocation.VerifyDeseralization()
SDK Version:
6.0.0
Reproduction steps:
ContentLocation.create(path_separator, path_segments)Expected behavior:
The manifest loads successfully.
Actual behavior:
A MismatchException is thrown:
Tableau.Migration.JsonConverters.Exceptions.MismatchException: Path should be Top-Level/ItemWith/SlashInName but is Top-Level/ItemWith\/SlashInName. at Tableau.Migration.JsonConverters.SerializableObjects.SerializableContentLocation.VerifyDeseralization() at Tableau.Migration.JsonConverters.SerializableObjects.SerializableContentLocation.AsContentLocation() at Tableau.Migration.JsonConverters.SerializableObjects.SerializableContentReference.AsContentReferenceStub() at Tableau.Migration.JsonConverters.SerializableObjects.SerializableManifestEntry...get_Source()Root cause analysis:
The manifest JSON serializer writes the / character inside item names as / — an optional but valid JSON escape sequence per the JSON specification (RFC 8259 §7). However, VerifyDeseralization() appears to reconstruct the expected path string from the content location segments (using unescaped /) and then compares it as a raw string against the value deserialized from JSON. Because the JSON parser returns the literal two-character sequence / rather than normalizing it to / before the comparison, the strings differ and the exception is thrown.
This is a round-trip bug: the serializer produces JSON the deserializer cannot accept, on the same machine, with no external changes to the manifest file.
Workaround:
Manually post-process manifest.json after each save, replacing all / occurrences with /. Since these are equivalent JSON representations of the same character, this is semantically safe but should not be required.