Repository: CycloneDX/cyclonedx-core-java
Pull request #486 (2024-08-25 02:19:45)

Fix Env vars de/serialization for formulation
This PR introduces key improvements following extensive testing across various scenarios.

It addresses issues with the de/serialization process for formulation, particularly in handling environment variables, which were previously inconsistent. To resolve these issues, a new class structure has been implemented that better aligns with the schema definition.

**Note**: This update includes some breaking changes. However, given the nature of the changes, they are unlikely to impact current usage, as no related issues have been reported thus far.


codacy-production[bot]: ## Coverage summary from Codacy
### [See diff coverage on Codacy](https://app.codacy.com/gh/CycloneDX/cyclonedx-core-java/coverage/pull-requests/486?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryPullRequest)

 | Coverage variation | Diff coverage | 
 | ------------- | ------------- |
 | :white_check_mark: **+0.35%** | :white_check_mark: **92.64%** |

<details>
<summary>Coverage variation details</summary>

| | Coverable lines | Covered lines | Coverage |
| ------------- | ------------- | ------------- | ------------- |
| Common ancestor commit (e8b3133db6f1fdff055be7ff9c9dde8642a09949) | 6029 | 4400 | 72.98% | |
| Head commit (b26d92b65374c509f66075bd85a137a4920edf33) | 6098 (+69) | 4472 (+72) | 73.34% (**+0.35%**) |

**Coverage variation** is the difference between the coverage for the head and common ancestor commits of the pull request branch: `<coverage of head commit> - <coverage of common ancestor commit>`
</details>

<details>
<summary>Diff coverage details</summary>

| | Coverable lines | Covered lines | Diff coverage |
| ------------- | ------------- | ------------- | ------------- |
| Pull request (#486) | 163 | 151 | **92.64%** |

**Diff coverage** is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: `<covered lines added or modified>/<coverable lines added or modified> * 100%`
</details>

#### [See your quality gate settings](https://app.codacy.com/gh/CycloneDX/cyclonedx-core-java/settings/quality?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryGates)&nbsp;&nbsp;&nbsp;&nbsp;[Change summary preferences](https://app.codacy.com/gh/CycloneDX/cyclonedx-core-java/settings/integrations?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummarySettings)
<sub>Codacy stopped sending the deprecated coverage status on June 5th, 2024. [Learn more](https://docs.codacy.com/release-notes/cloud/cloud-2023-11-23-new-coverage-engine-status-checks/#deprecation-and-removal-of-the-old-coverage-engine-status-checks)</sub>

<!-- 108f945d-e553-4918-8ccf-027c6d4acc2c -->
mr-zepol: Fixed Compilation Issues
nscuro: > @mr-zepol Note: This update includes some breaking changes. However, given the nature of the changes, they are unlikely to impact current usage, as no related issues have been reported thus far.

While I technically agree, I do think we need to take semver a bit more seriously. Last time we released breaking changes in a bugfix version (by my mistake). So I'd suggest we bump the library version to `10.0.0-SNAPSHOT` for this: 

https://github.com/CycloneDX/cyclonedx-core-java/blob/dde00242ae0271e38a0e4312fad0136d801f219c/pom.xml#L26

WDYT?
mr-zepol: > > @mr-zepol Note: This update includes some breaking changes. However, given the nature of the changes, they are unlikely to impact current usage, as no related issues have been reported thus far.
> 
> While I technically agree, I do think we need to take semver a bit more seriously. Last time we released breaking changes in a bugfix version (by my mistake). So I'd suggest we bump the library version to `10.0.0-SNAPSHOT` for this:
> 
> https://github.com/CycloneDX/cyclonedx-core-java/blob/dde00242ae0271e38a0e4312fad0136d801f219c/pom.xml#L26
> 
> WDYT?

Yes, that makes sense, so IMO what we should do is merge the other PRs we have, have a normal release, and then this one
mr-zepol: > @mr-zepol Good suggestion!

So I will create smaller PRs for this one that we can merge before introducing the actual breaking changes, so we have a release with all the improvements.
codacy-production[bot]: ## Coverage summary from Codacy
### [See diff coverage on Codacy](https://app.codacy.com/gh/CycloneDX/cyclonedx-core-java/coverage/pull-requests/486?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryPullRequest)

 | Coverage variation | Diff coverage | 
 | ------------- | ------------- |
 | :white_check_mark: **+0.69%** | :white_check_mark: **92.35%** |

<details>
<summary>Coverage variation details</summary>

| | Coverable lines | Covered lines | Coverage |
| ------------- | ------------- | ------------- | ------------- |
| Common ancestor commit (67e02c4b45ef293379e814db1c3a844cfdcadc3d) | 6071 | 4432 | 73.00% | |
| Head commit (64fec1784a3dc33616751d59921234b327bb46a5) | 6131 (+60) | 4518 (+86) | 73.69% (**+0.69%**) |

**Coverage variation** is the difference between the coverage for the head and common ancestor commits of the pull request branch: `<coverage of head commit> - <coverage of common ancestor commit>`
</details>

<details>
<summary>Diff coverage details</summary>

| | Coverable lines | Covered lines | Diff coverage |
| ------------- | ------------- | ------------- | ------------- |
| Pull request (#486) | 170 | 157 | **92.35%** |

**Diff coverage** is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: `<covered lines added or modified>/<coverable lines added or modified> * 100%`
</details>

#### [See your quality gate settings](https://app.codacy.com/gh/CycloneDX/cyclonedx-core-java/settings/quality?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryGates)&nbsp;&nbsp;&nbsp;&nbsp;[Change summary preferences](https://app.codacy.com/gh/CycloneDX/cyclonedx-core-java/settings/integrations?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummarySettings)
<sub>Codacy stopped sending the deprecated coverage status on June 5th, 2024. [Learn more](https://docs.codacy.com/release-notes/cloud/cloud-2023-11-23-new-coverage-engine-status-checks/#deprecation-and-removal-of-the-old-coverage-engine-status-checks)</sub>

<!-- 108f945d-e553-4918-8ccf-027c6d4acc2c -->
mr-zepol: @nscuro since we have a new release with the previous changes, this one can be merged now

Patch:
diff --git a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java
index e135dd69f..bc0f29beb 100644
--- a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java
@@ -140,6 +140,7 @@ public class BomXmlGenerator extends AbstractBomGenerator
      *
      * @return a String of the BoM
      * @since 1.1.0
+     *
      */
     @Override
     public String toString() {
diff --git a/src/main/java/org/cyclonedx/model/component/crypto/AlgorithmProperties.java b/src/main/java/org/cyclonedx/model/component/crypto/AlgorithmProperties.java
index cd9971809..24f08dae2 100644
--- a/src/main/java/org/cyclonedx/model/component/crypto/AlgorithmProperties.java
+++ b/src/main/java/org/cyclonedx/model/component/crypto/AlgorithmProperties.java
@@ -5,8 +5,8 @@ import java.util.Objects;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
-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.component.crypto.enums.CertificationLevel;
@@ -16,7 +16,6 @@ import org.cyclonedx.model.component.crypto.enums.ImplementationPlatform;
 import org.cyclonedx.model.component.crypto.enums.Mode;
 import org.cyclonedx.model.component.crypto.enums.Padding;
 import org.cyclonedx.model.component.crypto.enums.Primitive;
-import org.cyclonedx.util.deserializer.CertificationLevelDeserializer;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonInclude(JsonInclude.Include.NON_EMPTY)
@@ -44,8 +43,7 @@ public class AlgorithmProperties
 
   private ImplementationPlatform implementationPlatform;
 
-  @JsonDeserialize(using = CertificationLevelDeserializer.class)
-  private CertificationLevel certificationLevel;
+  private List<CertificationLevel> certificationLevel;
 
   private Mode mode;
 
@@ -97,11 +95,14 @@ public class AlgorithmProperties
     this.implementationPlatform = implementationPlatform;
   }
 
-  public CertificationLevel getCertificationLevel() {
+  @JacksonXmlElementWrapper(useWrapping = false)
+  @JacksonXmlProperty(localName = "certificationLevel")
+  @JsonProperty("certificationLevel")
+  public List<CertificationLevel> getCertificationLevel() {
     return certificationLevel;
   }
 
-  public void setCertificationLevel(final CertificationLevel certificationLevel) {
+  public void setCertificationLevel(final List<CertificationLevel> certificationLevel) {
     this.certificationLevel = certificationLevel;
   }
 
diff --git a/src/main/java/org/cyclonedx/model/formulation/common/AbstractType.java b/src/main/java/org/cyclonedx/model/formulation/common/AbstractType.java
index c537e169f..8e2561898 100644
--- a/src/main/java/org/cyclonedx/model/formulation/common/AbstractType.java
+++ b/src/main/java/org/cyclonedx/model/formulation/common/AbstractType.java
@@ -16,7 +16,7 @@ public abstract class AbstractType extends ExtensibleElement
   private ResourceReferenceChoice source;
   private ResourceReferenceChoice target;
   private ResourceReferenceChoice resource;
-  private List<EnvVariableChoice> environmentVars;
+  private EnvironmentVars environmentVars;
   private AttachmentText data;
   private List<Property> properties;
 
@@ -44,13 +44,12 @@ public abstract class AbstractType extends ExtensibleElement
     this.resource = resource;
   }
 
-
   @JacksonXmlElementWrapper(localName = "environmentVars")
-  public List<EnvVariableChoice> getEnvironmentVars() {
+  public EnvironmentVars getEnvironmentVars() {
     return environmentVars;
   }
 
-  public void setEnvironmentVars(final List<EnvVariableChoice> environmentVars) {
+  public void setEnvironmentVars(final EnvironmentVars environmentVars) {
     this.environmentVars = environmentVars;
   }
 
diff --git a/src/main/java/org/cyclonedx/model/formulation/common/EnvVariableChoice.java b/src/main/java/org/cyclonedx/model/formulation/common/EnvVariableChoice.java
deleted file mode 100644
index 87066cfd8..000000000
--- a/src/main/java/org/cyclonedx/model/formulation/common/EnvVariableChoice.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.cyclonedx.model.formulation.common;
-
-import java.util.Objects;
-
-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.JacksonXmlProperty;
-import org.cyclonedx.model.Property;
-import org.cyclonedx.util.deserializer.EnvVariableChoiceDeserializer;
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-@JsonInclude(JsonInclude.Include.NON_EMPTY)
-@JsonDeserialize(using = EnvVariableChoiceDeserializer.class)
-public class EnvVariableChoice
-{
-  @JacksonXmlProperty(localName = "value")
-  private String value;
-
-  @JacksonXmlProperty(localName = "environmentVar")
-  private Property environmentVar;
-
-  public String getValue() {
-    return value;
-  }
-
-  public void setValue(final String value) {
-    this.value = value;
-  }
-
-  public Property getEnvironmentVar() {
-    return environmentVar;
-  }
-
-  public void setEnvironmentVar(final Property environmentVar) {
-    this.environmentVar = environmentVar;
-  }
-
-  @Override
-  public boolean equals(final Object object) {
-    if (this == object) {
-      return true;
-    }
-    if (!(object instanceof EnvVariableChoice)) {
-      return false;
-    }
-    EnvVariableChoice that = (EnvVariableChoice) object;
-    return Objects.equals(value, that.value) && Objects.equals(environmentVar, that.environmentVar);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(value, environmentVar);
-  }
-}
\ No newline at end of file
diff --git a/src/main/java/org/cyclonedx/model/formulation/common/EnvironmentVars.java b/src/main/java/org/cyclonedx/model/formulation/common/EnvironmentVars.java
new file mode 100644
index 000000000..a9c3a0338
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/formulation/common/EnvironmentVars.java
@@ -0,0 +1,26 @@
+package org.cyclonedx.model.formulation.common;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.cyclonedx.util.deserializer.EnvironmentVarsDeserializer;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+@JsonDeserialize(using = EnvironmentVarsDeserializer.class)
+public class EnvironmentVars
+{
+  private List<Object> choices;
+
+  public List<Object> getChoices() {
+    return choices;
+  }
+
+  public void setChoices(List<Object> choices) {
+    this.choices = choices;
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/cyclonedx/util/deserializer/AbstractDataTypeDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/AbstractDataTypeDeserializer.java
index 5471d130f..5fa0b39fd 100644
--- a/src/main/java/org/cyclonedx/util/deserializer/AbstractDataTypeDeserializer.java
+++ b/src/main/java/org/cyclonedx/util/deserializer/AbstractDataTypeDeserializer.java
@@ -18,18 +18,20 @@
  */
 package org.cyclonedx.util.deserializer;
 
-import java.util.ArrayList;
+import java.io.IOException;
 import java.util.List;
 
+import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
+
 import com.fasterxml.jackson.databind.JsonDeserializer;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.DeserializationContext;
 import org.cyclonedx.model.Property;
 import org.cyclonedx.model.formulation.common.AbstractType;
-import org.cyclonedx.model.formulation.common.EnvVariableChoice;
+import org.cyclonedx.model.formulation.common.EnvironmentVars;
 import org.cyclonedx.model.formulation.common.ResourceReferenceChoice;
 
 public abstract class AbstractDataTypeDeserializer<T extends AbstractType>
@@ -37,19 +39,18 @@ public abstract class AbstractDataTypeDeserializer<T extends AbstractType>
 
   protected final ObjectMapper objectMapper = new ObjectMapper();
 
-  protected void setEnvironmentVars(final JsonNode node, AbstractType data) throws JsonProcessingException {
-    JsonNode nodes = node.get("environmentVars");
-    List<EnvVariableChoice> environmentVars = new ArrayList<>();
-
-    if (nodes != null) {
-      ArrayNode environmentVarsNode = DeserializerUtils.getArrayNode(nodes, objectMapper);
-      for (JsonNode envVarNode : environmentVarsNode) {
-        EnvVariableChoice envVar = objectMapper.treeToValue(envVarNode, EnvVariableChoice.class);
-        environmentVars.add(envVar);
-      }
-    }
+  private final EnvironmentVarsDeserializer environmentVarsDeserializer = new EnvironmentVarsDeserializer();
 
-    data.setEnvironmentVars(environmentVars);
+  protected void setEnvironmentVars(
+      final JsonNode node,
+      AbstractType data,
+      JsonParser jsonParser,
+      DeserializationContext ctxt) throws IOException
+  {
+    JsonNode nodes = node.get("environmentVars");
+    JsonParser nodeParser = nodes.traverse(jsonParser.getCodec());
+    EnvironmentVars envVar = environmentVarsDeserializer.deserialize(nodeParser, ctxt);
+    data.setEnvironmentVars(envVar);
   }
 
   protected void setReference(JsonNode node, String fieldName, AbstractType type) throws JsonProcessingException {
diff --git a/src/main/java/org/cyclonedx/util/deserializer/CertificationLevelDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/CertificationLevelDeserializer.java
deleted file mode 100644
index f77a7c8f0..000000000
--- a/src/main/java/org/cyclonedx/util/deserializer/CertificationLevelDeserializer.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.cyclonedx.util.deserializer;
-
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonNode;
-import org.cyclonedx.model.component.crypto.enums.CertificationLevel;
-
-public class CertificationLevelDeserializer
-    extends JsonDeserializer<CertificationLevel>
-{
-  @Override
-  public CertificationLevel deserialize(JsonParser parser, DeserializationContext context) throws IOException {
-    JsonNode node = parser.getCodec().readTree(parser);
-    JsonNode certificationLevelNode = node.has("certificationLevel") ? node.get("certificationLevel") : node;
-
-    String certificationLevelText = certificationLevelNode.isArray() ?
-        certificationLevelNode.get(0).asText() :
-        certificationLevelNode.asText();
-
-    return CertificationLevel.fromString(certificationLevelText);
-  }
-}
\ No newline at end of file
diff --git a/src/main/java/org/cyclonedx/util/deserializer/EnvVariableChoiceDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/EnvVariableChoiceDeserializer.java
deleted file mode 100644
index 725360011..000000000
--- a/src/main/java/org/cyclonedx/util/deserializer/EnvVariableChoiceDeserializer.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * This file is part of CycloneDX Core (Java).
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- * Copyright (c) OWASP Foundation. All Rights Reserved.
- */
-package org.cyclonedx.util.deserializer;
-
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonNode;
-import org.cyclonedx.model.Property;
-import org.cyclonedx.model.formulation.common.EnvVariableChoice;
-
-public class EnvVariableChoiceDeserializer
-    extends JsonDeserializer<EnvVariableChoice>
-{
-  @Override
-  public EnvVariableChoice deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
-    JsonNode node = jsonParser.getCodec().readTree(jsonParser);
-    EnvVariableChoice envReferenceChoice = new EnvVariableChoice();
-
-    if(node.isObject()) {
-      if (node.has("value") && node.has("name")) {
-        Property prop = createProperty(node);
-        envReferenceChoice.setEnvironmentVar(prop);
-      }
-      else if (node.has("environmentVar")) {
-        JsonNode envVarNode = node.get("environmentVar");
-        Property prop = createProperty(envVarNode);
-        envReferenceChoice.setEnvironmentVar(prop);
-      }
-    } else {
-      envReferenceChoice.setValue(node.asText());
-    }
-    return envReferenceChoice;
-  }
-
-  private Property createProperty(JsonNode envVarNode){
-    Property prop = new Property();
-
-    if (envVarNode.has("name")) {
-      String name = envVarNode.get("name").asText();
-      prop.setName(name);
-    }
-    if (envVarNode.has("")) {
-      String value = envVarNode.get("").asText();
-      prop.setValue(value);
-    } else  if (envVarNode.has("value")) {
-      String value = envVarNode.get("value").asText();
-      prop.setValue(value);
-    }
-    return prop;
-  }
-}
diff --git a/src/main/java/org/cyclonedx/util/deserializer/EnvironmentVarsDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/EnvironmentVarsDeserializer.java
new file mode 100644
index 000000000..4a2ad90f8
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/deserializer/EnvironmentVarsDeserializer.java
@@ -0,0 +1,108 @@
+/*
+ * This file is part of CycloneDX Core (Java).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) OWASP Foundation. All Rights Reserved.
+ */
+package org.cyclonedx.util.deserializer;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.cyclonedx.model.Property;
+
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.cyclonedx.model.formulation.common.EnvironmentVars;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EnvironmentVarsDeserializer extends StdDeserializer<EnvironmentVars> {
+
+  public EnvironmentVarsDeserializer() {
+    this(null);
+  }
+
+  public EnvironmentVarsDeserializer(Class<?> vc) {
+    super(vc);
+  }
+
+  @Override
+  public EnvironmentVars deserialize(JsonParser jp, DeserializationContext ctxt)
+      throws IOException{
+    JsonNode node = jp.getCodec().readTree(jp);
+    EnvironmentVars environmentVars = new EnvironmentVars();
+    List<Object> choices = new ArrayList<>();
+
+    if (node.isObject()) {
+      processObject(node, choices);
+    }
+    else if (node.isArray()) {
+      for (JsonNode n : node) {
+        processObject(n, choices);
+      }
+    }
+    else {
+      choices.add(node.asText());
+    }
+
+    environmentVars.setChoices(choices);
+    return environmentVars;
+  }
+
+  private void processObject(JsonNode node,  List<Object> choices ){
+    if (node.has("environmentVar") && node.has("value")) {
+      JsonNode envVarNode = node.get("environmentVar");
+      Property environmentVar = createProperty(envVarNode);
+      choices.add(environmentVar);
+      choices.add(node.get("value").asText());
+    }
+    else if (node.has("value")) {
+      if (node.has("name")) {
+        Property environmentVar = createProperty(node);
+        choices.add(environmentVar);
+      }
+      else {
+        choices.add(node.get("value").asText());
+      }
+    }
+    else if (node.has("environmentVar")) {
+      JsonNode envVarNode = node.get("environmentVar");
+      Property environmentVar = createProperty(envVarNode);
+      choices.add(environmentVar);
+    } else if(node.isTextual()) {
+      choices.add(node.asText());
+    }
+  }
+
+  private Property createProperty(JsonNode envVarNode){
+    Property prop = new Property();
+
+    if (envVarNode.has("name")) {
+      String name = envVarNode.get("name").asText();
+      prop.setName(name);
+    }
+    if (envVarNode.has("")) {
+      String value = envVarNode.get("").asText();
+      prop.setValue(value);
+    } else  if (envVarNode.has("value")) {
+      String value = envVarNode.get("value").asText();
+      prop.setValue(value);
+    }
+    return prop;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/deserializer/InputTypeDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/InputTypeDeserializer.java
index c4bed2f8e..3d8e355e9 100644
--- a/src/main/java/org/cyclonedx/util/deserializer/InputTypeDeserializer.java
+++ b/src/main/java/org/cyclonedx/util/deserializer/InputTypeDeserializer.java
@@ -26,10 +26,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonNode;
 import org.cyclonedx.model.AttachmentText;
-import org.cyclonedx.model.Property;
 import org.cyclonedx.model.formulation.common.InputType;
 import org.cyclonedx.model.formulation.common.InputType.Parameter;
-import org.cyclonedx.model.formulation.common.ResourceReferenceChoice;
 
 public class InputTypeDeserializer extends AbstractDataTypeDeserializer<InputType> {
 
@@ -41,13 +39,13 @@ public class InputTypeDeserializer extends AbstractDataTypeDeserializer<InputTyp
     InputType inputType = new InputType();
 
     setSourceAndTarget(node, inputType);
-    createInputDataInfo(node, inputType);
+    createInputDataInfo(node, inputType, jsonParser, deserializationContext);
     setProperties(node, inputType);
 
     return inputType;
   }
 
-  private void createInputDataInfo(JsonNode node, InputType inputType)
+  private void createInputDataInfo(JsonNode node, InputType inputType, JsonParser jsonParser, DeserializationContext ctxt)
       throws IOException
   {
     if (node.has("resource")) {
@@ -57,7 +55,7 @@ public class InputTypeDeserializer extends AbstractDataTypeDeserializer<InputTyp
       List<Parameter> parameters = objectMapper.convertValue(parametersNode, new TypeReference<List<Parameter>>() {});
       inputType.setParameters(parameters);
     } else if (node.has("environmentVars")) {
-      setEnvironmentVars(node, inputType);
+      setEnvironmentVars(node, inputType, jsonParser, ctxt);
     } else if (node.has("data")) {
       JsonNode dataNode = node.get("data");
       AttachmentText data = objectMapper.treeToValue(dataNode, AttachmentText.class);
diff --git a/src/main/java/org/cyclonedx/util/deserializer/OutputTypeDeserializer.java b/src/main/java/org/cyclonedx/util/deserializer/OutputTypeDeserializer.java
index 92d29a391..2cf4d7cb5 100644
--- a/src/main/java/org/cyclonedx/util/deserializer/OutputTypeDeserializer.java
+++ b/src/main/java/org/cyclonedx/util/deserializer/OutputTypeDeserializer.java
@@ -19,18 +19,13 @@
 package org.cyclonedx.util.deserializer;
 
 import java.io.IOException;
-import java.util.List;
 
 import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonNode;
 import org.cyclonedx.model.AttachmentText;
-import org.cyclonedx.model.Property;
 import org.cyclonedx.model.formulation.common.OutputType;
 import org.cyclonedx.model.formulation.common.OutputType.OutputTypeEnum;
-import org.cyclonedx.model.formulation.common.ResourceReferenceChoice;
 
 public class OutputTypeDeserializer
     extends AbstractDataTypeDeserializer<OutputType> {
@@ -43,7 +38,7 @@ public class OutputTypeDeserializer
     OutputType outputType = new OutputType();
 
     setSourceAndTarget(node, outputType);
-    createOutputDataInfo(node, outputType);
+    createOutputDataInfo(node, outputType, deserializationContext, jsonParser);
     setProperties(node, outputType);
 
     if(node.has("type")) {
@@ -55,12 +50,14 @@ public class OutputTypeDeserializer
     return outputType;
   }
 
-  private void createOutputDataInfo(JsonNode node, OutputType outputType) throws JsonProcessingException {
+  private void createOutputDataInfo(JsonNode node, OutputType outputType, DeserializationContext ctxt, JsonParser jsonParser)
+      throws IOException
+  {
     if (node.has("resource")) {
       setResource(node, outputType);
     }
     else if (node.has("environmentVars")) {
-      setEnvironmentVars(node, outputType);
+      setEnvironmentVars(node, outputType, jsonParser, ctxt);
     }
     else if (node.has("data")) {
       JsonNode dataNode = node.get("data");
diff --git a/src/main/java/org/cyclonedx/util/serializer/AbstractDataTypeSerializer.java b/src/main/java/org/cyclonedx/util/serializer/AbstractDataTypeSerializer.java
deleted file mode 100644
index abfd857f3..000000000
--- a/src/main/java/org/cyclonedx/util/serializer/AbstractDataTypeSerializer.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.cyclonedx.util.serializer;
-
-import java.io.IOException;
-import java.util.List;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ser.std.StdSerializer;
-import org.cyclonedx.model.formulation.common.AbstractType;
-import org.cyclonedx.model.formulation.common.EnvVariableChoice;
-
-
-public abstract class AbstractDataTypeSerializer<T extends AbstractType> extends StdSerializer<T>
-{
-
-  public AbstractDataTypeSerializer(Class<T> t) {
-    super(t);
-  }
-
-  protected void parseEnvironmentVars(final JsonGenerator jsonGenerator, final List<EnvVariableChoice> vars)
-      throws IOException
-  {
-    jsonGenerator.writeArrayFieldStart("environmentVars");
-    for (EnvVariableChoice envVarChoice : vars) {
-      if (envVarChoice.getEnvironmentVar() != null) {
-        jsonGenerator.writeStartObject();
-        jsonGenerator.writeObjectField("environmentVar", envVarChoice.getEnvironmentVar());
-        jsonGenerator.writeEndObject();
-      }
-      else if (envVarChoice.getValue() != null) {
-        jsonGenerator.writeStartObject();
-        jsonGenerator.writeObjectField("value", envVarChoice.getValue());
-        jsonGenerator.writeEndObject();
-      }
-    }
-    jsonGenerator.writeEndArray();
-  }
-
-  protected void writeField(JsonGenerator jsonGenerator, String fieldName, Object fieldValue) throws IOException {
-    if (fieldValue != null) {
-      jsonGenerator.writeFieldName(fieldName);
-      jsonGenerator.writeObject(fieldValue);
-    }
-  }
-
-}
diff --git a/src/main/java/org/cyclonedx/util/serializer/EnvironmentVarsSerializer.java b/src/main/java/org/cyclonedx/util/serializer/EnvironmentVarsSerializer.java
new file mode 100644
index 000000000..3f0c3bffa
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/serializer/EnvironmentVarsSerializer.java
@@ -0,0 +1,97 @@
+package org.cyclonedx.util.serializer;
+
+import java.io.IOException;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
+import org.cyclonedx.model.Property;
+import org.cyclonedx.model.formulation.common.EnvironmentVars;
+
+public class EnvironmentVarsSerializer
+    extends StdSerializer<EnvironmentVars>
+{
+  private final boolean isXml;
+
+  public EnvironmentVarsSerializer(boolean isXml) {
+    this(null, isXml);
+  }
+
+  public EnvironmentVarsSerializer(Class<EnvironmentVars> t, boolean isXml) {
+    super(t);
+    this.isXml = isXml;
+  }
+
+  @Override
+  public void serialize(EnvironmentVars value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
+      throws IOException {
+
+    List<Object> choices = value.getChoices();
+
+    if (isXml && jsonGenerator instanceof ToXmlGenerator) {
+      ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
+      serializeXml(choices, xmlGenerator);
+    } else {
+      serializeJson(choices, jsonGenerator);
+    }
+  }
+
+  private void serializeXml(List<Object> choices, ToXmlGenerator xmlGenerator) throws IOException {
+    xmlGenerator.writeFieldName("environmentVars");
+    if (choices.size() == 1 && choices.get(0) instanceof String) {
+      xmlGenerator.writeStartObject();
+      xmlGenerator.writeStringField("value", (String) choices.get(0));
+      xmlGenerator.writeEndObject();
+    } else {
+      xmlGenerator.writeStartObject();
+      for (Object choice : choices) {
+        if (choice instanceof Property) {
+          xmlGenerator.writeFieldName("environmentVar");
+          xmlGenerator.writeStartObject();
+          Property prop = (Property) choice;
+          xmlGenerator.setNextIsAttribute(true);
+          xmlGenerator.writeFieldName("name");
+          xmlGenerator.writeString(prop.getName());
+          xmlGenerator.setNextIsAttribute(false);
+
+          xmlGenerator.setNextIsUnwrapped(true);
+          xmlGenerator.writeStringField("", prop.getValue());
+          xmlGenerator.writeEndObject();
+        } else if (choice instanceof String) {
+          xmlGenerator.writeFieldName("value");
+          xmlGenerator.writeString((String) choice);
+        }
+      }
+      xmlGenerator.writeEndObject();
+    }
+  }
+
+  private void serializeJson(List<Object> choices, JsonGenerator jsonGenerator) throws IOException {
+    jsonGenerator.writeFieldName("environmentVars");
+    jsonGenerator.writeStartArray();
+    if (choices.size() == 1 && choices.get(0) instanceof String) {
+      jsonGenerator.writeString((String) choices.get(0));
+    } else {
+
+      for (Object choice : choices) {
+        if (choice instanceof Property) {
+          jsonGenerator.writeStartObject();
+          Property property = (Property) choice;
+          jsonGenerator.writeStringField("name", property.getName());
+          jsonGenerator.writeStringField("value", property.getValue());
+          jsonGenerator.writeEndObject();
+        } else if (choice instanceof String) {
+          jsonGenerator.writeString((String) choice);
+        }
+      }
+    }
+    jsonGenerator.writeEndArray();
+  }
+
+  @Override
+  public Class<EnvironmentVars> handledType() {
+    return EnvironmentVars.class;
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/cyclonedx/util/serializer/InputTypeSerializer.java b/src/main/java/org/cyclonedx/util/serializer/InputTypeSerializer.java
index 3b433c1be..7fc253c95 100644
--- a/src/main/java/org/cyclonedx/util/serializer/InputTypeSerializer.java
+++ b/src/main/java/org/cyclonedx/util/serializer/InputTypeSerializer.java
@@ -4,11 +4,12 @@ import java.io.IOException;
 
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
 import org.cyclonedx.model.formulation.common.InputType;
 
 public class InputTypeSerializer
-    extends AbstractDataTypeSerializer<InputType>
+    extends StdSerializer<InputType>
 {
   private final boolean isXml;
 
@@ -26,31 +27,31 @@ public class InputTypeSerializer
       throws IOException {
     if (isXml && jsonGenerator instanceof ToXmlGenerator) {
       ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
-      createInputChoice(value, xmlGenerator);
+      createInputChoice(value, xmlGenerator, serializerProvider);
     } else {
-      createInputChoice(value, jsonGenerator);
+      createInputChoice(value, jsonGenerator, serializerProvider);
     }
   }
 
-  private void createInputChoice(final InputType input, final JsonGenerator jsonGenerator)
+  private void createInputChoice(final InputType input, final JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
       throws IOException
   {
     jsonGenerator.writeStartObject();
 
     if (input.getResource() != null) {
       jsonGenerator.writeFieldName("resource");
-      jsonGenerator.writeObject( input.getResource());
+      jsonGenerator.writeObject(input.getResource());
     }
     else if (input.getParameters() != null && !input.getParameters().isEmpty()) {
       jsonGenerator.writeFieldName("parameters");
-      jsonGenerator.writeObject( input.getParameters());
+      jsonGenerator.writeObject(input.getParameters());
     }
-    else if (input.getEnvironmentVars() != null && !input.getEnvironmentVars().isEmpty()) {
-      parseEnvironmentVars(jsonGenerator, input.getEnvironmentVars());
+    else if (input.getEnvironmentVars() != null) {
+      new EnvironmentVarsSerializer(isXml).serialize(input.getEnvironmentVars(), jsonGenerator, serializerProvider);
     }
     else if (input.getData() != null) {
       jsonGenerator.writeFieldName("data");
-      jsonGenerator.writeObject( input.getData());
+      jsonGenerator.writeObject(input.getData());
     }
 
     writeField(jsonGenerator, "source", input.getSource());
@@ -60,6 +61,13 @@ public class InputTypeSerializer
     jsonGenerator.writeEndObject();
   }
 
+  protected void writeField(JsonGenerator jsonGenerator, String fieldName, Object fieldValue) throws IOException {
+    if (fieldValue != null) {
+      jsonGenerator.writeFieldName(fieldName);
+      jsonGenerator.writeObject(fieldValue);
+    }
+  }
+
   @Override
   public Class<InputType> handledType() {
     return InputType.class;
diff --git a/src/main/java/org/cyclonedx/util/serializer/OutputTypeSerializer.java b/src/main/java/org/cyclonedx/util/serializer/OutputTypeSerializer.java
index add9cb16d..a231cef4e 100644
--- a/src/main/java/org/cyclonedx/util/serializer/OutputTypeSerializer.java
+++ b/src/main/java/org/cyclonedx/util/serializer/OutputTypeSerializer.java
@@ -4,11 +4,12 @@ import java.io.IOException;
 
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
 import org.cyclonedx.model.formulation.common.OutputType;
 
 public class OutputTypeSerializer
-    extends AbstractDataTypeSerializer<OutputType>
+    extends StdSerializer<OutputType>
 {
   private final boolean isXml;
 
@@ -26,16 +27,13 @@ public class OutputTypeSerializer
       throws IOException {
     if (isXml && jsonGenerator instanceof ToXmlGenerator) {
       ToXmlGenerator xmlGenerator = (ToXmlGenerator) jsonGenerator;
-      xmlGenerator.writeStartObject();
-      xmlGenerator.writeFieldName("input");
-      createOutputChoice(value, xmlGenerator);
-      xmlGenerator.writeEndObject();
+      createOutputChoiceXml(value, xmlGenerator, serializerProvider);
     } else {
-      createOutputChoice(value, jsonGenerator);
+      createOutputChoiceJson(value, jsonGenerator, serializerProvider);
     }
   }
 
-  private void createOutputChoice(final OutputType output, final JsonGenerator jsonGenerator)
+  private void createOutputChoiceJson(final OutputType output, final JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
       throws IOException
   {
     jsonGenerator.writeStartObject();
@@ -44,8 +42,8 @@ public class OutputTypeSerializer
       jsonGenerator.writeFieldName("resource");
       jsonGenerator.writeObject( output.getResource());
     }
-    else if (output.getEnvironmentVars() != null && !output.getEnvironmentVars().isEmpty()) {
-      parseEnvironmentVars(jsonGenerator, output.getEnvironmentVars());
+    else if (output.getEnvironmentVars() != null) {
+      new EnvironmentVarsSerializer(isXml).serialize(output.getEnvironmentVars(), jsonGenerator, serializerProvider);
     }
     else if (output.getData() != null) {
       jsonGenerator.writeFieldName("data");
@@ -59,7 +57,48 @@ public class OutputTypeSerializer
     jsonGenerator.writeEndObject();
   }
 
+  private void createOutputChoiceXml(final OutputType output, final ToXmlGenerator xmlGenerator, SerializerProvider serializerProvider)
+      throws IOException
+  {
+    xmlGenerator.writeStartObject();
+
+    if (output.getResource() != null) {
+      xmlGenerator.writeFieldName("resource");
+      xmlGenerator.writeObject( output.getResource());
+    }
+    else if (output.getEnvironmentVars() != null) {
+      new EnvironmentVarsSerializer(isXml).serialize(output.getEnvironmentVars(), xmlGenerator, serializerProvider);
+    }
+    else if (output.getData() != null) {
+      xmlGenerator.writeFieldName("data");
+      xmlGenerator.writeObject( output.getData());
+    }
 
+    if (output.getType() != null) {
+      xmlGenerator.writeFieldName("type");
+      xmlGenerator.writeObject(output.getType());
+    }
+    if (output.getSource() != null) {
+      xmlGenerator.writeFieldName("source");
+      xmlGenerator.writeObject(output.getSource());
+    }
+    if (output.getTarget() != null) {
+      xmlGenerator.writeFieldName("target");
+      xmlGenerator.writeObject(output.getTarget());
+    }
+    if (output.getProperties() != null) {
+      xmlGenerator.writeFieldName("properties");
+      xmlGenerator.writeObject( output.getProperties());
+    }
+    xmlGenerator.writeEndObject();
+  }
+
+  protected void writeField(JsonGenerator jsonGenerator, String fieldName, Object fieldValue) throws IOException {
+    if (fieldValue != null) {
+      jsonGenerator.writeFieldName(fieldName);
+      jsonGenerator.writeObject(fieldValue);
+    }
+  }
 
   @Override
   public Class<OutputType> handledType() {
diff --git a/src/main/java/org/cyclonedx/util/serializer/SignatorySerializer.java b/src/main/java/org/cyclonedx/util/serializer/SignatorySerializer.java
index ea8ca3c46..4322c907b 100644
--- a/src/main/java/org/cyclonedx/util/serializer/SignatorySerializer.java
+++ b/src/main/java/org/cyclonedx/util/serializer/SignatorySerializer.java
@@ -62,8 +62,7 @@ public class SignatorySerializer
 
     if (signatory.getSignature() != null && !isXml) {
       shouldSerialize = true;
-    }
-    else if (signatory.getExternalReference() != null && signatory.getOrganization() != null) {
+    } else if (signatory.getExternalReference() != null && signatory.getOrganization() != null) {
       shouldSerialize = true;
     }
 


Test patch:
diff --git a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
index ae1207108..09aea41ee 100644
--- a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
@@ -362,6 +362,42 @@ public class BomJsonGeneratorTest {
         assertTrue(parser.isValid(loadedFile, version));
     }
 
+    @Test
+    public void schema16_testFormulation() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonXmlBom("/1.6/valid-formulation-1.6.xml");
+
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
+        File loadedFile = writeToFile(generator.toJsonString());
+
+        JsonParser parser = new JsonParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void schema16_testCrypto() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonXmlBom("/1.6/valid-cryptography-full-1.6.xml");
+
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
+        File loadedFile = writeToFile(generator.toJsonString());
+
+        JsonParser parser = new JsonParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void schema16_testML() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonXmlBom("/1.6/valid-machine-learning-1.6.xml");
+
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(version, bom);
+        File loadedFile = writeToFile(generator.toJsonString());
+
+        JsonParser parser = new JsonParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
     private void assertLicenseInformation(Bom bom, Version version) {
 
         //First Component
diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
index 3a4035239..6f6c54b5a 100644
--- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
@@ -480,6 +480,54 @@ public class BomXmlGeneratorTest {
         assertTrue(parser.isValid(loadedFile, version));
     }
 
+    @Test
+    public void schema16_testFormulation() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonJsonBom("/1.6/valid-formulation-1.6.json");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void schema16_testCompositions() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonJsonBom("/1.6/valid-compositions-1.6.json");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void schema16_testCrypto() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonJsonBom("/1.6/valid-cryptography-full-1.6.json");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
+    @Test
+    public void schema16_testML() throws Exception {
+        Version version = Version.VERSION_16;
+        Bom bom = createCommonJsonBom("/1.6/valid-machine-learning-1.6.json");
+
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(version, bom);
+        File loadedFile = writeToFile(generator.toXmlString());
+
+        XmlParser parser = new XmlParser();
+        assertTrue(parser.isValid(loadedFile, version));
+    }
+
     private void assertLicenseInformation(Bom bom, Version version) {
 
         //First Component
diff --git a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
index c4762d95c..94edd03a6 100644
--- a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
@@ -559,7 +559,7 @@ public class JsonParserTest
         assertEquals("128", ap.getParameterSetIdentifier());
         assertEquals(ExecutionEnvironment.SOFTWARE_PLAIN_RAM, ap.getExecutionEnvironment());
         assertEquals(ImplementationPlatform.X86_64, ap.getImplementationPlatform());
-        assertEquals(CertificationLevel.NONE, ap.getCertificationLevel());
+        assertEquals(CertificationLevel.NONE, ap.getCertificationLevel().get(0));
         assertEquals(Mode.GCM, ap.getMode());
         assertEquals(128, ap.getClassicalSecurityLevel());
         assertEquals(1, ap.getNistQuantumSecurityLevel());
diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
index c4a00fc47..c2aadeadd 100644
--- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
@@ -704,7 +704,7 @@ public class XmlParserTest
         assertEquals("128", ap.getParameterSetIdentifier());
         assertEquals(ExecutionEnvironment.SOFTWARE_PLAIN_RAM, ap.getExecutionEnvironment());
         assertEquals(ImplementationPlatform.X86_64, ap.getImplementationPlatform());
-        assertEquals(CertificationLevel.NONE, ap.getCertificationLevel());
+        assertEquals(CertificationLevel.NONE, ap.getCertificationLevel().get(0));
         assertEquals(Mode.GCM, ap.getMode());
         assertEquals(128, ap.getClassicalSecurityLevel());
         assertEquals(1, ap.getNistQuantumSecurityLevel());
diff --git a/src/test/resources/1.6/valid-formulation-1.6.json b/src/test/resources/1.6/valid-formulation-1.6.json
index ce49ece6d..0f8ee5a6f 100644
--- a/src/test/resources/1.6/valid-formulation-1.6.json
+++ b/src/test/resources/1.6/valid-formulation-1.6.json
@@ -250,7 +250,7 @@
           "workspaces": [
             {
               "bom-ref": "workspace-2",
-              "uid": "workspace-1",
+              "uid": "workspace-2",
               "name": "My workspace",
               "aliases": [ "default-workspace" ],
               "description": "Description here",