CycloneDX / CycloneDX/cyclonedx-cli
SPDX 2.3 conversion fails for valid primaryPackagePurpose: "OPERATING-SYSTEM"
- Dominant language
- C#
- Stars
- 541
- Forks
- 82
- PR merge metrics
- No merged PRs in 30d
Description
### Description
CycloneDX CLI fails to convert a valid SPDX 2.3 JSON document when a package contains the following field:
"primaryPackagePurpose": "OPERATING-SYSTEM"
OPERATING-SYSTEM is the valid value defined by the SPDX 2.3 specification.
The CLI throws an unhandled System.Text.Json.JsonException while deserializing the SPDX document.
Replacing the valid SPDX value: `OPERATING-SYSTEM` with the non-standard value: `OPERATING_SYSTEM` allows the conversion to proceed.
### Environment
CycloneDX CLI version: `0.32.0`
Platform: `Linux x64 / WSL Ubuntu 24.04`
Input format: `SPDX JSON 2.3`
Output format: `CycloneDX JSON`
Requested output version: `CycloneDX 1.6`
### Command
```
./cyclonedx-linux-x64 convert \
--input-format spdxjson \
--output-format json \
--output-version v1_6 \
--input-file minimal.spdx.json \
--output-file output.cdx.json
```
### Minimal reproducer
```
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "operating-system-test",
"documentNamespace": "https://example.com/spdx/operating-system-test",
"creationInfo": {
"created": "2026-07-14T08:00:00Z",
"creators": [
"Tool: minimal-reproducer"
]
},
"packages": [
{
"name": "Example Linux",
"SPDXID": "SPDXRef-OperatingSystem",
"versionInfo": "1.0",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"primaryPackagePurpose": "OPERATING-SYSTEM"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relationshipType": "DESCRIBES",
"relatedSpdxElement": "SPDXRef-OperatingSystem"
}
]
}
```
### Actual result
The conversion terminates with the following exception:
```
Unhandled exception: System.Text.Json.JsonException: The JSON value could not be converted to System.Nullable`1[CycloneDX.Spdx.Models.v2_3.PrimaryPackagePurposeType]. Path: $.packages[0].primaryPackagePurpose
at System.Text.Json.ThrowHelper.ThrowJsonException(String message)
at System.Text.Json.Serialization.Converters.EnumConverter`1.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
at System.Text.Json.Serialization.Converters.NullableConverter`1.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize[TReadBufferState,TStream](TReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, T& value)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync[TReadBufferState,TStream](TStream utf8Json, TReadBufferState bufferState, CancellationToken cancellationToken)
at CycloneDX.Spdx.Serialization.JsonSerializer.DeserializeAsync(Stream jsonStream)
at CycloneDX.Cli.CliUtils.InputBomHelper(String filename, ConvertFormat format)
at CycloneDX.Cli.Commands.ConvertCommand.Convert(ConvertCommandOptions options)
```
### Expected result
The CLI should successfully deserialize the SPDX 2.3 document and convert the package to a CycloneDX component.
### Analysis
The SPDX model defines the C# enum member as:
`OPERATING_SYSTEM`
while the SPDX 2.3 JSON representation requires:
`OPERATING-SYSTEM`
The SPDX JSON serializer currently registers `HyphenToUnderscoreEnumConverter` for some enum types, such as:
```
HyphenToUnderscoreEnumConverter
HyphenToUnderscoreEnumConverter
```
However, no equivalent converter appears to be registered for:
`PrimaryPackagePurposeType`
The generic `JsonStringEnumConverter` therefore attempts to match `OPERATING-SYSTEM` directly against the C# enum member `OPERATING_SYSTEM` and fails.
The issue likely originates in `CycloneDX.Spdx.Serialization.JsonSerializer` from the `cyclonedx-dotnet-library`, rather than in the CLI conversion logic itself.
### Possible fix
Register the existing converter for `PrimaryPackagePurposeType` before the generic `JsonStringEnumConverter`:
```
options.Converters.Add(
new HyphenToUnderscoreEnumConverter());
```
A regression test should verify both directions:
Deserializing:
```
"primaryPackagePurpose": "OPERATING-SYSTEM"
```
into:
```
PrimaryPackagePurposeType.OPERATING_SYSTEM
```
Serializing the enum back to the SPDX-compliant value:
`"primaryPackagePurpose": "OPERATING-SYSTEM"`
Workaround
As a temporary workaround, replacing:
`"primaryPackagePurpose": "OPERATING-SYSTEM"`
with:
`"primaryPackagePurpose": "OPERATING_SYSTEM"`
allows the conversion to complete, but the resulting temporary SPDX input is not compliant with the SPDX 2.3 specification.
Contributor guide
Assessment
This issue has not been assessed yet.