Repository: CycloneDX/cyclonedx-core-java
Pull request #627 (2025-04-10 14:59:25)

Fix wrong property type in `ComponentData.content`
This fixes issue https://github.com/CycloneDX/cyclonedx-core-java/issues/616


Patch:
diff --git a/src/main/java/org/cyclonedx/model/component/data/Content.java b/src/main/java/org/cyclonedx/model/component/data/Content.java
index 8177a6847..81853c5a6 100644
--- a/src/main/java/org/cyclonedx/model/component/data/Content.java
+++ b/src/main/java/org/cyclonedx/model/component/data/Content.java
@@ -2,13 +2,15 @@ package org.cyclonedx.model.component.data;
 
 import java.util.List;
 import java.util.Objects;
-import java.util.Properties;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import org.cyclonedx.model.AttachmentText;
+import org.cyclonedx.model.Property;
+import org.cyclonedx.util.deserializer.PropertiesDeserializer;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonInclude(JsonInclude.Include.NON_EMPTY)
@@ -18,7 +20,7 @@ public class Content
 
   private String url;
 
-  private List<Properties> properties;
+  private List<Property> properties;
 
   public AttachmentText getAttachment() {
     return attachment;
@@ -38,11 +40,12 @@ public class Content
 
   @JacksonXmlElementWrapper(localName = "properties")
   @JacksonXmlProperty(localName = "property")
-  public List<Properties> getProperties() {
+  @JsonDeserialize(using = PropertiesDeserializer.class)
+  public List<Property> getProperties() {
     return properties;
   }
 
-  public void setProperties(final List<Properties> properties) {
+  public void setProperties(final List<Property> properties) {
     this.properties = properties;
   }
 
diff --git a/src/main/java/org/cyclonedx/model/component/modelCard/ModelParameters.java b/src/main/java/org/cyclonedx/model/component/modelCard/ModelParameters.java
index 70aff3d93..6c8fcb91c 100644
--- a/src/main/java/org/cyclonedx/model/component/modelCard/ModelParameters.java
+++ b/src/main/java/org/cyclonedx/model/component/modelCard/ModelParameters.java
@@ -8,11 +8,13 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
 import org.cyclonedx.model.ExtensibleElement;
 import org.cyclonedx.model.component.data.DatasetChoice;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JacksonXmlRootElement(localName = "modelParameters")
 public class ModelParameters extends ExtensibleElement
 {
   private Approach approach;


Test patch:
diff --git a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
index b534aa211..d0efdbbea 100644
--- a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
@@ -48,8 +48,10 @@ import org.cyclonedx.model.component.crypto.enums.ExecutionEnvironment;
 import org.cyclonedx.model.component.crypto.enums.ImplementationPlatform;
 import org.cyclonedx.model.component.crypto.enums.Mode;
 import org.cyclonedx.model.component.crypto.enums.Primitive;
+import org.cyclonedx.model.component.data.Content;
 import org.cyclonedx.model.component.evidence.Identity;
 import org.cyclonedx.model.component.modelCard.Considerations;
+import org.cyclonedx.model.component.modelCard.ModelParameters;
 import org.cyclonedx.model.component.modelCard.consideration.EnvironmentalConsideration;
 import org.cyclonedx.model.component.modelCard.consideration.consumption.Activity;
 import org.cyclonedx.model.component.modelCard.consideration.consumption.EnergyConsumption;
@@ -352,6 +354,23 @@ public class JsonParserTest
         assertNull(eec.getProperties());
     }
 
+    @Test
+    public void schema16_ml() throws Exception {
+        final Bom bom = getJsonBom("1.6/valid-machine-learning-1.6.json");
+
+        assertNotNull(bom.getComponents());
+        ModelCard mc = bom.getComponents().get(0).getModelCard();
+        assertNotNull(mc);
+
+        ModelParameters mp = mc.getModelParameters();
+        assertNotNull(mp);
+
+        Content content = mp.getDatasets().get(0).getComponentData().getContents();
+        assertNotNull(content);
+
+        assertEquals(2, content.getProperties().size());
+    }
+
     @Test
     public void schema16_component_identifiers() throws Exception {
         final Bom bom = getJsonBom("1.6/valid-component-identifiers-1.6.json");
diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
index 6e79b4f94..c444fc5a5 100644
--- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
@@ -49,8 +49,10 @@ import org.cyclonedx.model.component.crypto.enums.ExecutionEnvironment;
 import org.cyclonedx.model.component.crypto.enums.ImplementationPlatform;
 import org.cyclonedx.model.component.crypto.enums.Mode;
 import org.cyclonedx.model.component.crypto.enums.Primitive;
+import org.cyclonedx.model.component.data.Content;
 import org.cyclonedx.model.component.evidence.Identity;
 import org.cyclonedx.model.component.modelCard.Considerations;
+import org.cyclonedx.model.component.modelCard.ModelParameters;
 import org.cyclonedx.model.component.modelCard.consideration.EnvironmentalConsideration;
 import org.cyclonedx.model.component.modelCard.consideration.consumption.Activity;
 import org.cyclonedx.model.component.modelCard.consideration.consumption.EnergyConsumption;
@@ -497,6 +499,23 @@ public class XmlParserTest
         assertNull(eec.getProperties());
     }
 
+    @Test
+    public void schema16_ml_content() throws Exception {
+        final Bom bom = getXmlBom("1.6/valid-machine-learning-1.6.xml");
+
+        assertNotNull(bom.getComponents());
+        ModelCard mc = bom.getComponents().get(0).getModelCard();
+        assertNotNull(mc);
+
+        ModelParameters mp = mc.getModelParameters();
+        assertNotNull(mp);
+
+        Content content = mp.getDatasets().get(0).getComponentData().getContents();
+        assertNotNull(content);
+
+        assertEquals(2, content.getProperties().size());
+    }
+
     @Test
     public void schema16_component_identifiers() throws Exception {
         final Bom bom = getXmlBom("1.6/valid-component-identifiers-1.6.xml");
diff --git a/src/test/resources/1.6/valid-machine-learning-1.6.json b/src/test/resources/1.6/valid-machine-learning-1.6.json
index dbd0ea7b6..2a621204f 100644
--- a/src/test/resources/1.6/valid-machine-learning-1.6.json
+++ b/src/test/resources/1.6/valid-machine-learning-1.6.json
@@ -26,7 +26,17 @@
               "type": "dataset",
               "name": "Training Data",
               "contents": {
-                "url": "https://example.com/path/to/dataset"
+                "url": "https://example.com/path/to/dataset",
+                "properties": [
+                  {
+                    "name": "Foo",
+                    "value": "Bar"
+                  },
+                  {
+                    "name": "Foo",
+                    "value": "Two"
+                  }
+                ]
               },
               "classification": "public"
             }
diff --git a/src/test/resources/1.6/valid-machine-learning-1.6.xml b/src/test/resources/1.6/valid-machine-learning-1.6.xml
index 6013b1c3d..d0346f210 100644
--- a/src/test/resources/1.6/valid-machine-learning-1.6.xml
+++ b/src/test/resources/1.6/valid-machine-learning-1.6.xml
@@ -21,6 +21,10 @@
                             <name>Training Data</name>
                             <contents>
                                 <url>https://example.com/path/to/dataset</url>
+                                <properties>
+                                    <property name="foo">bar</property>
+                                    <property name="foo1">bar2</property>
+                                </properties>
                             </contents>
                             <classification>public</classification>
                         </dataset>