Repository: CycloneDX/cyclonedx-core-java
Pull request #69 (2021-02-08 20:49:58)

Jackson-ForSure
This PR consolidates all the changes for the older `Jackson` branch, and supersedes the older branch.

Have at it! And please holler about any issues you discover.


Patch:
diff --git a/.gitignore b/.gitignore
index 78575d1ba..db47dfc46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 target
 *.iml
+.idea/
+dependency-reduced-pom.xml
diff --git a/README.md b/README.md
index fe96a36f7..6cb78095b 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Maven Usage
 <dependency>
     <groupId>org.cyclonedx</groupId>
     <artifactId>cyclonedx-core-java</artifactId>
-    <version>3.0.8</version>
+    <version>3.0.7</version>
 </dependency>
 ```
 
diff --git a/pom.xml b/pom.xml
index 768cd5d21..0c6490339 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <groupId>org.cyclonedx</groupId>
     <artifactId>cyclonedx-core-java</artifactId>
     <packaging>jar</packaging>
-    <version>3.0.9-SNAPSHOT</version>
+    <version>3.0.8-SNAPSHOT</version>
 
     <name>CycloneDX Core (Java)</name>
     <description>The CycloneDX core module provides a model representation of the BOM along with utilities to assist in creating, parsing, and validating BOMs.</description>
@@ -63,9 +63,8 @@
         <maven.cyclonedx.plugin.version>2.1.1</maven.cyclonedx.plugin.version>
         <maven.javadoc.plugin.version>3.2.0</maven.javadoc.plugin.version>
         <maven.source.plugin.version>3.2.1</maven.source.plugin.version>
-        <maven.jar.plugin.version>3.2.0</maven.jar.plugin.version>
+        <maven.jar.plugin.version>3.0.2</maven.jar.plugin.version>
         <maven.github.release.plugin.version>1.4.0</maven.github.release.plugin.version>
-        <project.build.outputTimestamp>2021-01-31T05:51:02Z</project.build.outputTimestamp>
     </properties>
 
     <scm>
@@ -149,18 +148,19 @@
             <artifactId>packageurl-java</artifactId>
             <version>1.1.1</version>
         </dependency>
-        <!-- XML Parser -->
+        <!-- XML Parser, Generator, JSON Parser -->
         <dependency>
-            <groupId>com.thoughtworks.xstream</groupId>
-            <artifactId>xstream</artifactId>
-            <version>1.4.14</version>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-xml</artifactId>
+            <version>2.12.0</version>
         </dependency>
+
         <dependency>
             <groupId>xerces</groupId>
             <artifactId>xercesImpl</artifactId>
             <version>2.12.0</version>
         </dependency>
-        <!-- JSON library -->
+        <!-- JSON Generator library -->
         <dependency>
             <groupId>org.json</groupId>
             <artifactId>json</artifactId>
@@ -172,12 +172,7 @@
             <artifactId>org.everit.json.schema</artifactId>
             <version>1.12.1</version>
         </dependency>
-        <!-- JSON Serialization / Deserialization -->
-        <dependency>
-            <groupId>com.alibaba</groupId>
-            <artifactId>fastjson</artifactId>
-            <version>1.2.75</version>
-        </dependency>
+
         <!-- Unit Test -->
         <dependency>
             <groupId>junit</groupId>
@@ -289,20 +284,6 @@
                 </configuration>
             </plugin>
         </plugins>
-        <pluginManagement>
-            <plugins>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-jar-plugin</artifactId>
-                    <version>${maven.jar.plugin.version}</version>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-release-plugin</artifactId>
-                    <version>3.0.0-M1</version>
-                </plugin>
-            </plugins>
-        </pluginManagement>
     </build>
 
     <profiles>
diff --git a/src/main/java/org/cyclonedx/converters/AttachmentTextConverter.java b/src/main/java/org/cyclonedx/converters/AttachmentTextConverter.java
deleted file mode 100644
index 5bb814f6d..000000000
--- a/src/main/java/org/cyclonedx/converters/AttachmentTextConverter.java
+++ /dev/null
@@ -1,54 +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) Steve Springett. All Rights Reserved.
- */
-package org.cyclonedx.converters;
-
-import com.thoughtworks.xstream.converters.Converter;
-import com.thoughtworks.xstream.converters.MarshallingContext;
-import com.thoughtworks.xstream.converters.UnmarshallingContext;
-import com.thoughtworks.xstream.io.HierarchicalStreamReader;
-import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
-import org.cyclonedx.model.AttachmentText;
-
-public class AttachmentTextConverter implements Converter {
-
-    @Override
-    public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) {
-        final AttachmentText attachmentText = (AttachmentText)object;
-        writer.addAttribute("content-type", attachmentText.getContentType());
-        writer.addAttribute("encoding", attachmentText.getEncoding());
-        writer.setValue(attachmentText.getText());
-    }
-
-    @Override
-    public boolean canConvert(Class clazz) {
-        return clazz.equals(AttachmentText.class);
-    }
-
-    @Override
-    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
-        final String contentType = reader.getAttribute("content-type");
-        final String encoding = reader.getAttribute("encoding");
-        final String text = (reader.getValue());
-        final AttachmentText attachmentText = new AttachmentText();
-        attachmentText.setContentType(contentType);
-        attachmentText.setEncoding(encoding);
-        attachmentText.setText(text);
-        return attachmentText;
-    }
-}
diff --git a/src/main/java/org/cyclonedx/converters/DependencyDeserializer.java b/src/main/java/org/cyclonedx/converters/DependencyDeserializer.java
deleted file mode 100644
index 35a095766..000000000
--- a/src/main/java/org/cyclonedx/converters/DependencyDeserializer.java
+++ /dev/null
@@ -1,46 +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) Steve Springett. All Rights Reserved.
- */
-package org.cyclonedx.converters;
-
-import com.alibaba.fastjson.parser.DefaultJSONParser;
-import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
-import org.cyclonedx.model.Dependency;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.List;
-
-public class DependencyDeserializer implements ObjectDeserializer {
-
-    @SuppressWarnings("unchecked")
-    public <T> T deserialze(final DefaultJSONParser parser, final Type type, final Object fieldName) {
-        final List<String> parseObjectList = parser.parseArray(String.class);
-        if(parseObjectList != null) {
-            final List<Dependency> dependencies = new ArrayList<>();
-            for(String ref: parseObjectList) {
-                dependencies.add(new Dependency(ref));
-            }
-            return (T)dependencies;
-        }
-        throw new IllegalStateException();
-    }
-
-    public int getFastMatchToken() {
-        return 0;
-    }
-}
diff --git a/src/main/java/org/cyclonedx/converters/HashConverter.java b/src/main/java/org/cyclonedx/converters/HashConverter.java
deleted file mode 100644
index 72803d660..000000000
--- a/src/main/java/org/cyclonedx/converters/HashConverter.java
+++ /dev/null
@@ -1,48 +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) Steve Springett. All Rights Reserved.
- */
-package org.cyclonedx.converters;
-
-import com.thoughtworks.xstream.converters.Converter;
-import com.thoughtworks.xstream.converters.MarshallingContext;
-import com.thoughtworks.xstream.converters.UnmarshallingContext;
-import com.thoughtworks.xstream.io.HierarchicalStreamReader;
-import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
-import org.cyclonedx.model.Hash;
-
-public class HashConverter implements Converter {
-
-    @Override
-    public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) {
-        Hash hash = (Hash)object;
-        writer.addAttribute("alg", hash.getAlgorithm());
-        writer.setValue(hash.getValue());
-    }
-
-    @Override
-    public boolean canConvert(Class clazz) {
-        return clazz.equals(Hash.class);
-    }
-
-    @Override
-    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
-        String alg = reader.getAttribute("alg");
-        String value = (reader.getValue());
-        return new Hash(alg, value);
-    }
-}
diff --git a/src/main/java/org/cyclonedx/exception/GeneratorException.java b/src/main/java/org/cyclonedx/exception/GeneratorException.java
new file mode 100644
index 000000000..7819226cb
--- /dev/null
+++ b/src/main/java/org/cyclonedx/exception/GeneratorException.java
@@ -0,0 +1,56 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.exception;
+
+/**
+ * Exception throws when parsing CycloneDX BoMs.
+ *
+ * @author Steve Springett
+ * @since 4.0.0
+ */
+public class GeneratorException extends Exception {
+
+  /**
+   * Constructs a new exception.
+   * @param message the detail message.
+   * @since 4.0.0
+   */
+  public GeneratorException(String message) {
+    super(message);
+  }
+
+  /**
+   * Constructs a new exception.
+   * @param cause the cause
+   * @since 4.0.0
+   */
+  public GeneratorException(Throwable cause) {
+    super(cause);
+  }
+
+  /**
+   * Constructs a new exception.
+   * @param message the detail message.
+   * @param cause the cause
+   * @since 4.0.0
+   */
+  public GeneratorException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java b/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
index 93ee94a28..5e0d405a8 100644
--- a/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
@@ -18,309 +18,49 @@
  */
 package org.cyclonedx.generators.json;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
 import org.cyclonedx.CycloneDxSchema;
-import org.cyclonedx.model.AttachmentText;
-import org.cyclonedx.model.Commit;
-import org.cyclonedx.model.Component;
-import org.cyclonedx.model.Dependency;
-import org.cyclonedx.model.ExternalReference;
-import org.cyclonedx.model.Hash;
-import org.cyclonedx.model.IdentifiableActionType;
-import org.cyclonedx.model.License;
-import org.cyclonedx.model.LicenseChoice;
-import org.cyclonedx.model.Metadata;
-import org.cyclonedx.model.OrganizationalContact;
-import org.cyclonedx.model.OrganizationalEntity;
-import org.cyclonedx.model.Pedigree;
-import org.cyclonedx.model.Swid;
-import org.cyclonedx.model.Tool;
-import org.json.JSONArray;
+import org.cyclonedx.exception.GeneratorException;
+import org.cyclonedx.model.Bom;
+import org.cyclonedx.util.CollectionTypeSerializer;
+import org.cyclonedx.util.LicenseChoiceSerializer;
 import org.json.JSONObject;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
 
 abstract class AbstractBomJsonGenerator extends CycloneDxSchema implements BomJsonGenerator {
 
-    private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
+    private final ObjectMapper mapper;
 
-    JSONObject doc = OrderedJSONObjectFactory.create();
-
-    void createMetadataNode(final JSONObject doc, final Metadata metadata) {
-        if (metadata != null) {
-            final JSONObject mdo = OrderedJSONObjectFactory.create();
-            if (metadata.getTimestamp() != null) {
-                mdo.put("timestamp", dateFormat.format(metadata.getTimestamp()));
-            } else {
-                mdo.put("timestamp", dateFormat.format(new Date()));
-            }
-            if (metadata.getTools() != null && metadata.getTools().size() > 0) {
-                final JSONArray jsonTools = new JSONArray();
-                for (final Tool tool: metadata.getTools()) {
-                    final JSONObject jsonTool = OrderedJSONObjectFactory.create();
-                    jsonTool.put("vendor", stripBreaks(tool.getVendor()));
-                    jsonTool.put("name", stripBreaks(tool.getName()));
-                    jsonTool.put("version", stripBreaks(tool.getVersion()));
-                    createHashesNode(jsonTool, tool.getHashes());
-                    jsonTools.put(jsonTool);
-                }
-                mdo.put("tools", jsonTools);
-            }
-            if (metadata.getAuthors() != null && metadata.getAuthors().size() > 0) {
-                final JSONArray jsonAuthors = new JSONArray();
-                for (final OrganizationalContact author: metadata.getAuthors()) {
-                    jsonAuthors.put(createOrganizationalContactNode(author));
-                }
-                mdo.put("authors", jsonAuthors);
-            }
-            if (metadata.getComponent() != null) {
-                mdo.put("component", createComponentNode(metadata.getComponent()));
-            }
-            mdo.put("manufacture", createOrganizationalEntityNode(metadata.getManufacture()));
-            mdo.put("supplier", createOrganizationalEntityNode(metadata.getSupplier()));
-            doc.put("metadata", mdo);
-        }
-    }
-
-    void createComponentsNode(final JSONObject json, final List<Component> components, final String nodeName) {
-        if (components != null && !components.isEmpty()) {
-            final JSONArray jsonComponents = new JSONArray();
-            for (Component component: components) {
-                jsonComponents.put(createComponentNode(component));
-            }
-            json.put(nodeName, jsonComponents);
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    JSONObject createComponentNode(final Component component) {
-        if (component == null) {
-            return null;
-        }
-        final JSONObject json = OrderedJSONObjectFactory.create();
-        json.put("type", component.getType().getTypeName());
-        json.put("bom-ref", stripBreaks(component.getBomRef()));
-        json.put("mime-type", stripBreaks(component.getMimeType()));
-        json.put("supplier", createOrganizationalEntityNode(component.getSupplier()));
-        json.put("author", stripBreaks(component.getAuthor()));
-        json.put("publisher", stripBreaks(component.getPublisher()));
-        json.put("group", stripBreaks(component.getGroup()));
-        json.put("name", stripBreaks(component.getName()));
-        json.put("version", stripBreaks(component.getVersion()));
-        json.put("description", stripBreaks(component.getDescription()));
-        if (component.getScope() != null) {
-            json.put("scope", component.getScope().getScopeName());
-        }
-        createHashesNode(json, component.getHashes());
-        createLicenseNode(json, component.getLicenseChoice());
-        json.put("copyright", stripBreaks(component.getCopyright()));
-        json.put("cpe", stripBreaks(component.getCpe()));
-        json.put("purl", stripBreaks(component.getPurl()));
-        createSwidNode(json, component.getSwid());
-        if (this.getSchemaVersion().getVersion() == 1.0 || component.isModified()) {
-            json.put("modified", component.isModified());
-        }
-        createPedigreeNode(json, component.getPedigree());
-        createExternalReferencesNode(json, component.getExternalReferences());
-        createComponentsNode(json, component.getComponents(), "components");
-        return json;
-    }
+    AbstractBomJsonGenerator() {
+        this.mapper = new ObjectMapper();
 
-    private void createHashesNode(final JSONObject parent, final List<Hash> hashes) {
-        if (hashes != null && !hashes.isEmpty()) {
-            final JSONArray jsonHashes = new JSONArray();
-            for (Hash hash : hashes) {
-                final JSONObject jsonHash = OrderedJSONObjectFactory.create();
-                jsonHash.put("alg", hash.getAlgorithm());
-                jsonHash.put("content", hash.getValue());
-                jsonHashes.put(jsonHash);
-            }
-            parent.put("hashes", jsonHashes);
-        }
+        setupObjectMapper(this.mapper);
     }
 
-    private void createLicenseNode(final JSONObject parent, final LicenseChoice licenseChoice) {
-        if (licenseChoice != null) {
-            final JSONArray jsonLicenses = new JSONArray();
-            if (licenseChoice.getLicenses() != null && !licenseChoice.getLicenses().isEmpty()) {
-                for (License license : licenseChoice.getLicenses()) {
-                    final JSONObject wrapper = new JSONObject();
-                    final JSONObject jsonLicense = OrderedJSONObjectFactory.create();
-                    if (license.getId() != null) {
-                        jsonLicense.put("id", license.getId());
-                    } else if (license.getName() != null) {
-                        jsonLicense.put("name", stripBreaks(license.getName()));
-                    }
-                    if (license.getAttachmentText() != null && license.getAttachmentText().getText() != null) {
-                        createAttachmentTextNode(jsonLicense, license.getAttachmentText());
-                    }
-                    jsonLicense.put("url", stripBreaks(license.getUrl()));
-                    wrapper.put("license", jsonLicense);
-                    jsonLicenses.put(wrapper);
-                }
-            } else if (licenseChoice.getExpression() != null) {
-                final JSONObject jsonLicense = OrderedJSONObjectFactory.create();
-                jsonLicense.put("expression", stripBreaks(licenseChoice.getExpression()));
-                jsonLicenses.put(jsonLicense);
-            }
-            parent.put("licenses", jsonLicenses);
-        }
-    }
+    private void setupObjectMapper(final ObjectMapper mapper) {
+        SimpleModule licenseModule = new SimpleModule();
+        SimpleModule depModule = new SimpleModule();
 
-    private void createSwidNode(final JSONObject jsonComponent, final Swid swid) {
-        if (swid != null) {
-            final JSONObject jsonSwid = OrderedJSONObjectFactory.create();
-            jsonSwid.put("tagId", stripBreaks(swid.getTagId()));
-            jsonSwid.put("name", stripBreaks(swid.getName()));
-            jsonSwid.put("version", stripBreaks(swid.getVersion()));
-            jsonSwid.put("tagVersion", swid.getTagVersion());
-            jsonSwid.put("patch", swid.isPatch());
-            createAttachmentTextNode(jsonSwid, swid.getAttachmentText());
-            jsonComponent.put("swid", jsonSwid);
-        }
-    }
+        licenseModule.addSerializer(new LicenseChoiceSerializer());
 
-    private void createAttachmentTextNode(final JSONObject json, final AttachmentText attachment) {
-        if (attachment != null && attachment.getText() != null) {
-            final JSONObject jsonAttachment = OrderedJSONObjectFactory.create();
-            jsonAttachment.put("contentType", stripBreaks(attachment.getContentType()));
-            jsonAttachment.put("encoding", stripBreaks(attachment.getEncoding()));
-            jsonAttachment.put("content", attachment.getText());
-            json.put("text", jsonAttachment);
-        }
-    }
+        mapper.registerModule(licenseModule);
 
-    void createExternalReferencesNode(final JSONObject parent, final List<ExternalReference> references) {
-        if (references != null && !references.isEmpty()) {
-            final JSONArray jsonReferences = new JSONArray();
-            for (ExternalReference reference : references) {
-                final JSONObject jsonReference = OrderedJSONObjectFactory.create();
-                jsonReference.put("type", reference.getType().getTypeName());
-                jsonReference.put("url", stripBreaks(reference.getUrl()));
-                jsonReference.put("comment", stripBreaks(reference.getComment()));
-                jsonReferences.put(jsonReference);
-            }
-            parent.put("externalReferences", jsonReferences);
-        }
+        depModule.setSerializers(new CollectionTypeSerializer(false));
+        mapper.registerModule(depModule);
     }
 
-    private void createPedigreeNode(final JSONObject parent, final Pedigree pedigree) {
-        if (pedigree != null) {
-            final JSONObject jsonPedigree = OrderedJSONObjectFactory.create();
-            if (pedigree.getAncestors() != null && !pedigree.getAncestors().isEmpty()) {
-                createComponentsNode(jsonPedigree, pedigree.getAncestors(), "ancestors");
-            }
-            if (pedigree.getDescendants() != null && !pedigree.getDescendants().isEmpty()) {
-                createComponentsNode(jsonPedigree, pedigree.getDescendants(), "descendants");
-            }
-            if (pedigree.getVariants() != null && !pedigree.getVariants().isEmpty()) {
-                createComponentsNode(jsonPedigree, pedigree.getVariants(), "variants");
-            }
-            if (pedigree.getCommits() != null && !pedigree.getCommits().isEmpty()) {
-                createCommitsNode(jsonPedigree, pedigree.getCommits());
-            }
-            jsonPedigree.put("notes", stripBreaks(pedigree.getNotes()));
-            parent.put("pedigree", jsonPedigree);
-        }
-    }
-
-    private void createCommitsNode(final JSONObject parent, List<Commit> commits) {
-        if (commits != null && !commits.isEmpty()) {
-            final JSONArray jsonCommits = new JSONArray();
-            for (Commit commit: commits) {
-                final JSONObject jsonCommit = OrderedJSONObjectFactory.create();
-                jsonCommit.put("uid", stripBreaks(commit.getUid()));
-                jsonCommit.put("url", stripBreaks(commit.getUrl()));
-                createActorNode(jsonCommit, "author", commit.getAuthor());
-                createActorNode(jsonCommit, "committer", commit.getCommitter());
-                jsonCommit.put("message", stripBreaks(commit.getMessage()));
-                jsonCommits.put(jsonCommit);
-            }
-            parent.put("commits", jsonCommits);
-        }
-    }
-
-    private void createActorNode(final JSONObject parent, final String nodeName, final IdentifiableActionType actor) {
-        if (actor != null) {
-            final JSONObject json = OrderedJSONObjectFactory.create();
-            json.put("timestamp", dateFormat.format(actor.getTimestamp()));
-            json.put("name", stripBreaks(actor.getName()));
-            json.put("email", stripBreaks(actor.getEmail()));
-            parent.put(nodeName, json);
-        }
-    }
-
-    private JSONObject createOrganizationalContactNode(final OrganizationalContact contact) {
-        if (contact != null) {
-            final JSONObject jsonContact = OrderedJSONObjectFactory.create();
-            jsonContact.put("name", stripBreaks(contact.getName()));
-            jsonContact.put("email", stripBreaks(contact.getEmail()));
-            jsonContact.put("phone", stripBreaks(contact.getPhone()));
-            return jsonContact;
-        } else {
-            return null;
-        }
-    }
-
-    private JSONArray createOrganizationalContactsNode(final List<OrganizationalContact> contacts) {
-        if (contacts != null) {
-            final JSONArray array = new JSONArray();
-            for (OrganizationalContact contact: contacts) {
-                array.put(createOrganizationalContactNode(contact));
-            }
-            return array;
-        } else {
-            return null;
-        }
-    }
-
-    private JSONObject createOrganizationalEntityNode(final OrganizationalEntity entity) {
-        if (entity != null) {
-            final JSONObject jsonEntity = OrderedJSONObjectFactory.create();
-            jsonEntity.put("name", stripBreaks(entity.getName()));
-            jsonEntity.put("url", Collections.singletonList(stripBreaks(entity.getUrl())));
-            jsonEntity.put("contact", createOrganizationalContactsNode(entity.getContacts()));
-            return jsonEntity;
-        } else {
-            return null;
-        }
-    }
+    JSONObject doc = OrderedJSONObjectFactory.create();
 
-    void createDependenciesNode(final JSONObject parent, final List<Dependency> dependencies) {
-        if (dependencies != null && !dependencies.isEmpty()) {
-            final JSONArray jsonDependencies = new JSONArray();
-            for (Dependency dependency : dependencies) {
-                final JSONObject dependencyNode = OrderedJSONObjectFactory.create();
-                dependencyNode.put("ref", dependency.getRef());
-                if (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty()) {
-                    final JSONArray jsonDependsOn = new JSONArray();
-                    for (Dependency dependsOn : dependency.getDependencies()) {
-                        jsonDependsOn.put(dependsOn.getRef());
-                    }
-                    dependencyNode.put("dependsOn", jsonDependsOn);
-                }
-                jsonDependencies.put(dependencyNode);
+    String toJson(final Bom bom, final boolean prettyPrint) throws GeneratorException {
+        try {
+            if (prettyPrint) {
+                return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(bom);
             }
-            parent.put("dependencies", jsonDependencies);
+            return mapper.writeValueAsString(bom);
         }
-    }
-
-    static String stripBreaks(String in) {
-        if (in == null) {
-            return null;
+        catch (JsonProcessingException e) {
+            throw new GeneratorException(e);
         }
-        return in.trim().replace("\r\n", " ").replace("\n", " ").replace("\t", " ").replace("\r", " ").replaceAll(" +", " ");
-    }
-
-    public String toJsonString() {
-        return doc.toString(2);
     }
-
-    @Override
-    public String toString() {
-        return doc.toString(0);
-    }
-
 }
diff --git a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java
index c8289c953..32fb7b4f3 100644
--- a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java
+++ b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java
@@ -18,7 +18,10 @@
  */
 package org.cyclonedx.generators.json;
 
+import java.lang.reflect.Field;
+
 import org.cyclonedx.CycloneDxSchema;
+import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.Component;
 import org.json.JSONObject;
@@ -38,7 +41,13 @@ public class BomJsonGenerator12 extends AbstractBomJsonGenerator implements BomJ
      * @param bom the BOM to generate
      */
     public BomJsonGenerator12(final Bom bom) {
-        this.bom = bom;
+        Bom modifiedBom = null;
+        try {
+            modifiedBom = injectBomFormatAndSpecVersion(bom);
+        }
+        catch (GeneratorException e) {
+        }
+        this.bom = modifiedBom != null ? modifiedBom : bom;
     }
 
     /**
@@ -55,16 +64,49 @@ public class BomJsonGenerator12 extends AbstractBomJsonGenerator implements BomJ
      * @since 3.0.0
      */
     public JSONObject generate() {
-        doc.put("bomFormat", "CycloneDX");
-        doc.put("specVersion", Version.VERSION_12.getVersionString());
-        if (bom.getSerialNumber() != null) {
-            doc.put("serialNumber", bom.getSerialNumber());
+        try {
+            doc = new JSONObject(toJson(this.bom, false));
+
+            return doc;
+        }
+        catch (GeneratorException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public String toJsonString() {
+        try {
+            return toJson(this.bom, true);
+        }
+        catch (GeneratorException e) {
+            return "";
+        }
+    }
+
+    @Override
+    public String toString() {
+        try {
+            return toJson(this.bom, false);
+        }
+        catch (GeneratorException e) {
+            return "";
+        }
+    }
+
+    private Bom injectBomFormatAndSpecVersion(Bom bom) throws GeneratorException {
+        try {
+            Field field;
+            field = Bom.class.getDeclaredField("bomFormat");
+            field.setAccessible(true);
+            field.set(bom, "CycloneDX");
+            field = Bom.class.getDeclaredField("specVersion");
+            field.setAccessible(true);
+            field.set(bom, getSchemaVersion().getVersionString());
+            return bom;
+        }
+        catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new GeneratorException(e);
         }
-        doc.put("version", bom.getVersion());
-        createMetadataNode(doc, bom.getMetadata());
-        createComponentsNode(doc, bom.getComponents(), "components");
-        createExternalReferencesNode(doc, bom.getExternalReferences());
-        createDependenciesNode(doc, bom.getDependencies());
-        return doc;
     }
 }
diff --git a/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java b/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java
index 15243e9f7..8faf176d3 100644
--- a/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/xml/AbstractBomXmlGenerator.java
@@ -18,51 +18,61 @@
  */
 package org.cyclonedx.generators.xml;
 
+import java.io.IOException;
+import java.io.StringReader;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
 import org.cyclonedx.CycloneDxSchema;
-import org.cyclonedx.model.Attribute;
-import org.cyclonedx.model.Commit;
-import org.cyclonedx.model.Component;
-import org.cyclonedx.model.Dependency;
-import org.cyclonedx.model.ExtensibleElement;
-import org.cyclonedx.model.ExtensibleType;
-import org.cyclonedx.model.ExternalReference;
-import org.cyclonedx.model.Hash;
-import org.cyclonedx.model.IdentifiableActionType;
-import org.cyclonedx.model.License;
-import org.cyclonedx.model.LicenseChoice;
-import org.cyclonedx.model.Metadata;
-import org.cyclonedx.model.OrganizationalContact;
-import org.cyclonedx.model.OrganizationalEntity;
-import org.cyclonedx.model.Pedigree;
-import org.cyclonedx.model.Swid;
-import org.cyclonedx.model.Tool;
-import org.cyclonedx.util.BomUtils;
+import org.cyclonedx.exception.GeneratorException;
+import org.cyclonedx.model.Bom;
+import org.cyclonedx.util.CollectionTypeSerializer;
+import org.cyclonedx.util.VersionAnnotationIntrospector;
 import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
 import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.List;
 
 abstract class AbstractBomXmlGenerator extends CycloneDxSchema implements BomXmlGenerator {
 
-    private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
+    private final ObjectMapper mapper;
+
+    AbstractBomXmlGenerator() {
+        mapper = new XmlMapper();
+        setupObjectMapper(mapper);
+    }
 
     Document doc;
 
+    protected static final String PROLOG = "<?xml version=\"1.0\"?>";
+
+    private void setupObjectMapper(final ObjectMapper mapper) {
+        mapper.setAnnotationIntrospector(
+            new VersionAnnotationIntrospector(
+                String.valueOf(this.getSchemaVersion().getVersion())));
+
+        if (this.getSchemaVersion().getVersion() == 1.0) {
+            // NO-OP
+        } else if (this.getSchemaVersion().getVersion() == 1.1) {
+            registerDependencyModule(mapper, true);
+        } else if (this.getSchemaVersion().getVersion() == 1.2) {
+            registerDependencyModule(mapper, false);
+        }
+    }
+
+    private void registerDependencyModule(final ObjectMapper mapper, final boolean useNamespace) {
+        SimpleModule depModule = new SimpleModule();
+
+        depModule.setSerializers(new CollectionTypeSerializer(useNamespace));
+        mapper.registerModule(depModule);
+    }
+
     /**
      * Constructs a new document builder with security features enabled.
      *
@@ -79,413 +89,36 @@ abstract class AbstractBomXmlGenerator extends CycloneDxSchema implements BomXml
         return factory.newDocumentBuilder();
     }
 
-    Element createElement(Node parent, String name) {
-        final Element node = doc.createElement(name);
-        parent.appendChild(node);
-        return node;
-    }
-
-    Element createElementNS(Node parent, String namespace, String name) {
-        final Element node = doc.createElementNS(namespace, name);
-        parent.appendChild(node);
-        return node;
-    }
-
-    Element createElement(Node parent, String name, Object value) {
-        return createElement(parent, name, value, new Attribute[0]);
-    }
-
-    Element createElement(Node parent, String name, Object value, boolean insideCDATA) {
-        return createElement(parent, name, value, insideCDATA, new Attribute[0]);
-    }
-
-    Element createElement(Node parent, String name, Object value, Attribute... attributes) {
-        return createElement(parent, name, value, false, attributes);
-    }
-
-    Element createElement(Node parent, String name, Object value, boolean insideCDATA, Attribute... attributes) {
-        Element node = null;
-        if (value != null || attributes.length > 0) {
-            if (name.contains(":")) {
-                node = doc.createElement(name);
-            } else {
-                node = doc.createElementNS(getSchemaVersion().getNamespace(), name);
-            }
-            for (Attribute attribute: attributes) {
-                node.setAttribute(attribute.getKey(), attribute.getValue());
-            }
-            if (value != null) {
-                if (insideCDATA) {
-                    node.appendChild(doc.createCDATASection(value.toString()));
-                } else {
-                    node.appendChild(doc.createTextNode(value.toString()));
-                }
-            }
-            parent.appendChild(node);
-        }
-        return node;
-    }
-
-    void createComponentsNode(Node parent, List<Component> components) {
-        if (components != null && !components.isEmpty()) {
-            for (Component component : components) {
-                createComponentNode(parent, component);
-            }
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    private void createComponentNode(Node parent, Component component) {
-        if (component == null) {
-            return;
-        }
-        final List<Attribute> componentAttrs = new ArrayList<>();
-        componentAttrs.add(new Attribute("type", component.getType().getTypeName()));
-        if (component.getBomRef() != null) {
-            componentAttrs.add(new Attribute("bom-ref", component.getBomRef()));
-        }
-        if (component.getMimeType() != null && this.getSchemaVersion().getVersion() >= 1.2) {
-            componentAttrs.add(new Attribute("mime-type", component.getMimeType()));
-        }
-        final Element componentNode = createElement(parent, "component", null, componentAttrs.toArray(new Attribute[0]));
-        if (this.getSchemaVersion().getVersion() >= 1.2) {
-            createOrganizationalEntityNode(componentNode, component.getSupplier(), "supplier");
-            createElement(componentNode, "author", stripBreaks(component.getAuthor()));
-        }
-        createElement(componentNode, "publisher", stripBreaks(component.getPublisher()));
-        createElement(componentNode, "group", stripBreaks(component.getGroup()));
-        createElement(componentNode, "name", stripBreaks(component.getName()));
-        createElement(componentNode, "version", stripBreaks(component.getVersion()));
-        createElement(componentNode, "description", stripBreaks(component.getDescription()), true);
-        if (component.getScope() == null) {
-            createElement(componentNode, "scope", Component.Scope.REQUIRED.getScopeName());
-        } else {
-            createElement(componentNode, "scope", component.getScope().getScopeName());
-        }
-        createHashesNode(componentNode, component.getHashes());
-        createLicenseNode(componentNode, component.getLicenseChoice());
-        createElement(componentNode, "copyright", stripBreaks(component.getCopyright()));
-        createElement(componentNode, "cpe", stripBreaks(component.getCpe()));
-        createElement(componentNode, "purl", stripBreaks(component.getPurl()));
-        if (this.getSchemaVersion().getVersion() == 1.0 || component.isModified()) {
-            createElement(componentNode, "modified", component.isModified());
-        }
-        if (this.getSchemaVersion().getVersion() >= 1.2) {
-            createSwidNode(componentNode, component.getSwid());
-        }
-        if (this.getSchemaVersion().getVersion() >= 1.1) {
-            createPedigreeNode(componentNode, component.getPedigree());
-            createExternalReferencesNode(componentNode, component.getExternalReferences());
-        }
-        if (component.getComponents() != null && !component.getComponents().isEmpty()) {
-            final Element subComponentsNode = createElement(componentNode, "components");
-            createComponentsNode(subComponentsNode, component.getComponents());
-        }
-        processExtensions(componentNode, component);
-    }
-
-    private void createHashesNode(Node parent, List<Hash> hashes) {
-        if (hashes != null && !hashes.isEmpty()) {
-            final Element hashesNode = createElement(parent, "hashes");
-            for (Hash hash : hashes) {
-                createElement(hashesNode, "hash", hash.getValue(), new Attribute("alg", hash.getAlgorithm()));
-            }
-        }
-    }
-
-    private void createLicenseNode(Node parent, LicenseChoice licenseChoice) {
-        if (licenseChoice != null) {
-            final Element licensesNode = doc.createElement("licenses");
-            if (licenseChoice.getLicenses() != null) {
-                parent.appendChild(licensesNode);
-                for (License license : licenseChoice.getLicenses()) {
-                    // Create individual license node
-                    final Element licenseNode = doc.createElement("license");
-                    if (license.getId() != null) {
-                        final Element licenseIdNode = doc.createElement("id");
-                        licenseIdNode.appendChild(doc.createTextNode(license.getId()));
-                        licenseNode.appendChild(licenseIdNode);
-                    } else if (license.getName() != null) {
-                        final Element licenseNameNode = doc.createElement("name");
-                        licenseNameNode.appendChild(doc.createTextNode(license.getName()));
-                        licenseNode.appendChild(licenseNameNode);
-                    }
-                    if (this.getSchemaVersion().getVersion() >= 1.1) {
-                        if (license.getAttachmentText() != null && license.getAttachmentText().getText() != null) {
-                            final Element licenseTextNode = doc.createElement("text");
-                            if (license.getAttachmentText().getContentType() != null) {
-                                licenseTextNode.setAttribute("content-type", license.getAttachmentText().getContentType());
-                            }
-                            if (license.getAttachmentText().getEncoding() != null) {
-                                licenseTextNode.setAttribute("encoding", license.getAttachmentText().getEncoding());
-                                if ("base64".equals(license.getAttachmentText().getEncoding())) {
-                                    licenseTextNode.appendChild(doc.createTextNode(license.getAttachmentText().getText()));
-                                } else {
-                                    licenseTextNode.appendChild(doc.createCDATASection(license.getAttachmentText().getText()));
-                                }
-                            } else {
-                                licenseTextNode.appendChild(doc.createCDATASection(license.getAttachmentText().getText()));
-                            }
-                            licenseNode.appendChild(licenseTextNode);
-                        }
-                        if (BomUtils.validateUrlString(license.getUrl())) {
-                            final Element licenseUrlNode = doc.createElement("url");
-                            licenseUrlNode.appendChild(doc.createTextNode(license.getUrl()));
-                            licenseNode.appendChild(licenseUrlNode);
-                        }
-
-                        processExtensions(licenseNode, license);
-                    }
-                    licensesNode.appendChild(licenseNode);
-                }
-            } else if (this.getSchemaVersion().getVersion() >= 1.1 && licenseChoice.getExpression() != null) {
-                createElement(licensesNode, "expression", stripBreaks(licenseChoice.getExpression()));
-            }
-        }
-    }
-
-    private void createSwidNode(Node parent, Swid swid) {
-        if (swid != null) {
-            final Element swidNode = createElement(parent, "swid");
-            if (swid.getTagId() != null) {
-                swidNode.setAttribute("tagId", swid.getTagId());
-            }
-            if (swid.getName() != null) {
-                swidNode.setAttribute("name", swid.getName());
-            }
-            if (swid.getVersion() != null) {
-                swidNode.setAttribute("version", swid.getVersion());
-            }
-            if (swid.getTagVersion() > 0) {
-                swidNode.setAttribute("tagVersion", String.valueOf(swid.getTagVersion()));
-            }
-            if (swid.isPatch()) {
-                swidNode.setAttribute("patch", "true");
-            }
-            if (swid.getAttachmentText() != null && swid.getAttachmentText().getText() != null) {
-                final Element attachmentTextNode = doc.createElement("text");
-                if (swid.getAttachmentText().getContentType() != null) {
-                    attachmentTextNode.setAttribute("content-type", swid.getAttachmentText().getContentType());
-                }
-                if (swid.getAttachmentText().getEncoding() != null) {
-                    attachmentTextNode.setAttribute("encoding", swid.getAttachmentText().getEncoding());
-                    if ("base64".equals(swid.getAttachmentText().getEncoding())) {
-                        attachmentTextNode.appendChild(doc.createTextNode(swid.getAttachmentText().getText()));
-                    } else {
-                        attachmentTextNode.appendChild(doc.createCDATASection(swid.getAttachmentText().getText()));
-                    }
-                } else {
-                    attachmentTextNode.appendChild(doc.createCDATASection(swid.getAttachmentText().getText()));
-                }
-                swidNode.appendChild(attachmentTextNode);
-            }
-        }
-    }
-
-    private void createPedigreeNode(Node parent, Pedigree pedigree) {
-        if (pedigree != null) {
-            final Element pedigreeNode = createElement(parent, "pedigree");
-            if (pedigree.getAncestors() != null && !pedigree.getAncestors().isEmpty()) {
-                final Element ancestorsNode = createElement(pedigreeNode, "ancestors");
-                createComponentsNode(ancestorsNode, pedigree.getAncestors());
-            }
-            if (pedigree.getDescendants() != null && !pedigree.getDescendants().isEmpty()) {
-                final Element descendantsNode = createElement(pedigreeNode, "descendants");
-                createComponentsNode(descendantsNode, pedigree.getDescendants());
-            }
-            if (pedigree.getVariants() != null && !pedigree.getVariants().isEmpty()) {
-                final Element variantsNode = createElement(pedigreeNode, "variants");
-                createComponentsNode(variantsNode, pedigree.getVariants());
-            }
-            if (pedigree.getCommits() != null && !pedigree.getCommits().isEmpty()) {
-                final Element commitsNode = createElement(pedigreeNode, "commits");
-                createCommitsNode(commitsNode, pedigree.getCommits());
-            }
-            createElement(pedigreeNode, "notes", stripBreaks(pedigree.getNotes()));
-            processExtensions(pedigreeNode, pedigree);
-        }
-    }
-
-    private void createCommitsNode(Node parent, List<Commit> commits) {
-        if (commits != null) {
-            for (Commit commit: commits) {
-                final Element commitNode = createElement(parent, "commit");
-                createElement(commitNode, "uid", stripBreaks(commit.getUid()));
-                createElement(commitNode, "url", stripBreaks(commit.getUrl()));
-                createActorNode(commitNode, "author", commit.getAuthor());
-                createActorNode(commitNode, "committer", commit.getCommitter());
-                createElement(commitNode, "message", stripBreaks(commit.getMessage()));
-                processExtensions(commitNode, commit);
-            }
-        }
-    }
-
-    private void createActorNode(Node parent, String nodeName, IdentifiableActionType actor) {
-        if (actor != null) {
-            final Element authorNode = createElement(parent, nodeName);
-            if (actor.getTimestamp() != null) {
-                createElement(authorNode, "timestamp", dateFormat.format(actor.getTimestamp()));
-            }
-            createElement(authorNode, "name", stripBreaks(actor.getName()));
-            createElement(authorNode, "email", stripBreaks(actor.getEmail()));
-            processExtensions(authorNode, actor);
-        }
-    }
-
-    void createExternalReferencesNode(Node parent, List<ExternalReference> references) {
-        if (references != null && !references.isEmpty()) {
-            final Element externalReferencesNode = createElement(parent, "externalReferences");
-            for (ExternalReference reference: references) {
-                if (reference.getType() != null && BomUtils.validateUrlString(reference.getUrl())) {
-                    final Element referenceNode = createElement(externalReferencesNode, "reference", null, new Attribute("type", reference.getType().getTypeName()));
-                    createElement(referenceNode, "url", stripBreaks(reference.getUrl()));
-                    createElement(referenceNode, "comment", stripBreaks(reference.getComment()));
-                }
-            }
-        }
-    }
-
-    void createDependenciesNode(Node parent, List<Dependency> dependencies) {
-        if (dependencies != null && !dependencies.isEmpty()) {
-            for (Dependency dependency : dependencies) {
-                final String nodeName = (this.getSchemaVersion().getVersion() >= 1.2) ? "dependency" : "dg:dependency";
-                final Element dependencyNode = createElement(parent, nodeName, null, new Attribute("ref", dependency.getRef()));
-                if (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty()) {
-                    createDependenciesNode(dependencyNode, dependency.getDependencies());
-                }
-            }
-        }
-    }
-
-    void createMetadataNode(Node parent, Metadata metadata) {
-        if (metadata != null) {
-            final Element metadataNode = createElement(parent, "metadata");
-            if (metadata.getTimestamp() != null) {
-                createElement(metadataNode, "timestamp", dateFormat.format(metadata.getTimestamp()));
-            }
-            createToolsNode(metadataNode, metadata.getTools());
-            createOrganizationalContactsNode(metadataNode, metadata.getAuthors(), "authors", "author");
-            createComponentNode(metadataNode, metadata.getComponent());
-            createOrganizationalEntityNode(metadataNode, metadata.getManufacture(), "manufacture");
-            createOrganizationalEntityNode(metadataNode, metadata.getSupplier(), "supplier");
-            processExtensions(metadataNode, metadata);
-        }
-    }
-
-    private void createToolsNode(Node parent, List<Tool> tools) {
-        if (tools != null && !tools.isEmpty()) {
-            final Element toolsNode = createElement(parent, "tools");
-            for (Tool tool : tools) {
-                final Element toolNode = createElement(toolsNode, "tool");
-                createElement(toolNode, "vendor", stripBreaks(tool.getVendor()));
-                createElement(toolNode, "name", stripBreaks(tool.getName()));
-                createElement(toolNode, "version", stripBreaks(tool.getVersion()));
-                createHashesNode(toolNode, tool.getHashes());
-                processExtensions(toolNode, tool);
-            }
-        }
-    }
-
-    private void createOrganizationalContactsNode(Node parent, List<OrganizationalContact> contacts, String parentElementName, String elementName) {
-        if (contacts != null && !contacts.isEmpty()) {
-            final Element contactsNode = createElement(parent, parentElementName);
-            for (OrganizationalContact contact : contacts) {
-                createOrganizationalContactNode(contactsNode, contact, elementName);
-            }
-        }
-    }
+    protected Document generateDocument(final Bom bom)
+        throws ParserConfigurationException
+    {
+        try {
+            final DocumentBuilder docBuilder = buildSecureDocumentBuilder();
 
-    private void createOrganizationalContactNode(Node parent, OrganizationalContact contact, String elementName) {
-        final Element contactNode = createElement(parent, elementName);
-        createElement(contactNode, "name", stripBreaks(contact.getName()));
-        createElement(contactNode, "email", stripBreaks(contact.getEmail()));
-        createElement(contactNode, "phone", stripBreaks(contact.getPhone()));
-        processExtensions(contactNode, contact);
-    }
+            this.doc = docBuilder.parse(new InputSource(new StringReader(toXML(bom, false))));
 
-    private void createOrganizationalEntityNode(Node parent, OrganizationalEntity entity, String elementName) {
-        if (entity != null) {
-            final Element entityNode = createElement(parent, elementName);
-            createElement(entityNode, "name", stripBreaks(entity.getName()));
-            createElement(entityNode, "url", stripBreaks(entity.getUrl()));
-            if (entity.getContacts() != null && entity.getContacts().size() > 0) {
-                createOrganizationalContactNode(entityNode, entity.getContacts().get(0), "contact");
-            }
-            processExtensions(entityNode, entity);
-        }
-    }
+            this.doc.setXmlStandalone(true);
 
-    protected void processExtensions(final Node node, final ExtensibleElement extensibleElement) {
-        if (extensibleElement != null && extensibleElement.getExtensibleTypes() != null) {
-            for (final ExtensibleType t: extensibleElement.getExtensibleTypes()) {
-                final Element e = createElementNS(node, t.getNamespace(), t.getName());
-                if (t.getAttributes() != null) {
-                    for (Attribute attr: t.getAttributes()) {
-                        e.setAttribute(attr.getKey(), attr.getValue());
-                    }
-                }
-                if (t.getValue() != null) {
-                    e.appendChild(doc.createTextNode(t.getValue()));
-                } else if (t.getExtensibleTypes() != null) {
-                    for (final ExtensibleType childType: t.getExtensibleTypes()) {
-                        final Element childNode = createElementNS(e, childType.getNamespace(), childType.getName());
-                        processExtensions(childNode, childType);
-                    }
-                }
-            }
+            return this.doc;
+        } catch (SAXException | ParserConfigurationException | IOException | GeneratorException ex) {
+            throw new ParserConfigurationException(ex.toString());
         }
     }
 
-    Element createRootElement(String name, Object value, Attribute... attributes) {
-        Element node = null;
-        if (value != null || attributes.length > 0) {
-            node = doc.createElementNS(getSchemaVersion().getNamespace(), name);
-            for (Attribute attribute: attributes) {
-                node.setAttribute(attribute.getKey(), attribute.getValue());
-            }
-            if (value != null) {
-                node.appendChild(doc.createTextNode(value.toString()));
+    String toXML(final Bom bom, final boolean prettyPrint) throws GeneratorException {
+        try {
+            if (prettyPrint) {
+                return PROLOG + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(bom);
             }
-            doc.appendChild(node);
-        }
-        return node;
-    }
-
-    static String stripBreaks(String in) {
-        if (in == null) {
-            return null;
-        }
-        return in.trim().replace("\r\n", " ").replace("\n", " ").replace("\t", " ").replace("\r", " ").replaceAll(" +", " ");
-    }
-
-    /**
-     * Creates a text representation of a CycloneDX BoM Document.
-     * @return a String of the BoM
-     * @throws TransformerException an TransformerException
-     * @since 1.1.0
-     */
-    public String toXmlString() throws TransformerException {
-        if (doc == null) {
-            return null;
+            return PROLOG + mapper.writeValueAsString(bom);
+        } catch (JsonProcessingException ex) {
+            throw new GeneratorException(ex);
         }
-        final DOMSource domSource = new DOMSource(doc);
-        final StringWriter writer = new StringWriter();
-        final StreamResult result = new StreamResult(writer);
-        final TransformerFactory tf = TransformerFactory.newInstance();
-        tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-        final Transformer transformer = tf.newTransformer();
-        transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
-        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-        transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
-        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
-        transformer.transform(domSource, result);
-
-        return writer.toString();
     }
 
     /**
      * Creates a text representation of a CycloneDX BoM Document. This method
-     * calls {@link #toXmlString()} and will return null if {@link #toXmlString()}
+     * calls {@link #toXmlString()} and will return an empty string if {@link #toXmlString()}
      * throws an exception. Its preferred to call {@link #toXmlString()} directly
      * so that exceptions can be caught.
      * @return a String of the BoM
@@ -495,7 +128,7 @@ abstract class AbstractBomXmlGenerator extends CycloneDxSchema implements BomXml
     public String toString() {
         try {
             return toXmlString();
-        } catch (TransformerException e) {
+        } catch (Exception e) {
             return "";
         }
     }
diff --git a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java
index b8170e7eb..8df8afa9a 100644
--- a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator.java
@@ -19,9 +19,9 @@
 package org.cyclonedx.generators.xml;
 
 import org.cyclonedx.CycloneDxSchema;
+import org.cyclonedx.exception.GeneratorException;
 import org.w3c.dom.Document;
 import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
 
 public interface BomXmlGenerator {
 
@@ -29,7 +29,7 @@ public interface BomXmlGenerator {
 
     Document generate() throws ParserConfigurationException;
 
-    String toXmlString() throws TransformerException;
+    String toXmlString() throws GeneratorException;
 
     String toString();
 
diff --git a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator10.java b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator10.java
index f22771110..471331be0 100644
--- a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator10.java
+++ b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator10.java
@@ -19,12 +19,10 @@
 package org.cyclonedx.generators.xml;
 
 import org.cyclonedx.CycloneDxSchema;
-import org.cyclonedx.model.Attribute;
+import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.Component;
 import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.ParserConfigurationException;
 
 /**
@@ -42,6 +40,8 @@ public class BomXmlGenerator10 extends AbstractBomXmlGenerator implements BomXml
      * @param bom the BOM to generate
      */
     public BomXmlGenerator10(final Bom bom) {
+        bom.setXmlns(CycloneDxSchema.NS_BOM_10);
+
         this.bom = bom;
     }
 
@@ -59,17 +59,10 @@ public class BomXmlGenerator10 extends AbstractBomXmlGenerator implements BomXml
      * @since 1.1.0
      */
     public Document generate() throws ParserConfigurationException {
-        final DocumentBuilder docBuilder = buildSecureDocumentBuilder();
-        this.doc = docBuilder.newDocument();
-        doc.setXmlStandalone(true);
-
-        // Create root <bom> node
-        final Element bomNode = createRootElement("bom", null,
-                new Attribute("xmlns", NS_BOM_10),
-                new Attribute("version", String.valueOf(bom.getVersion())));
+        return generateDocument(this.bom);
+    }
 
-        final Element componentsNode = createElement(bomNode, "components");
-        createComponentsNode(componentsNode, bom.getComponents());
-        return doc;
+    public String toXmlString() throws GeneratorException {
+        return toXML(this.bom, true);
     }
 }
diff --git a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator11.java b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator11.java
index ba02bb64c..5ed493a42 100644
--- a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator11.java
+++ b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator11.java
@@ -19,15 +19,11 @@
 package org.cyclonedx.generators.xml;
 
 import org.cyclonedx.CycloneDxSchema;
-import org.cyclonedx.model.Attribute;
+import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.Component;
 import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.ParserConfigurationException;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * BomGenerator creates a CycloneDX bill-of-material document from a set of
@@ -44,6 +40,8 @@ public class BomXmlGenerator11 extends AbstractBomXmlGenerator implements BomXml
      * @param bom the BOM to generate
      */
     public BomXmlGenerator11(final Bom bom) {
+        bom.setXmlns(CycloneDxSchema.NS_BOM_11);
+
         this.bom = bom;
     }
 
@@ -61,32 +59,10 @@ public class BomXmlGenerator11 extends AbstractBomXmlGenerator implements BomXml
      * @since 2.0.0
      */
     public Document generate() throws ParserConfigurationException {
-        final DocumentBuilder docBuilder = buildSecureDocumentBuilder();
-        this.doc = docBuilder.newDocument();
-        doc.setXmlStandalone(true);
-
-        final List<Attribute> attributes = new ArrayList<>();
-        attributes.add(new Attribute("xmlns", NS_BOM_11));
-        attributes.add(new Attribute("version", String.valueOf(bom.getVersion())));
-        if (bom.getDependencies() != null && bom.getDependencies().size() > 0) {
-            attributes.add(new Attribute("xmlns:dg", NS_DEPENDENCY_GRAPH_10));
-        }
-
-        // Create root <bom> node
-        final Element bomNode = createRootElement("bom", null, attributes.toArray(new Attribute[0]));
-        if (bom.getSerialNumber() != null) {
-            bomNode.setAttribute("serialNumber", bom.getSerialNumber());
-        }
-
-        final Element componentsNode = createElement(bomNode, "components");
-        createComponentsNode(componentsNode, bom.getComponents());
-        createExternalReferencesNode(bomNode, bom.getExternalReferences());
+        return generateDocument(this.bom);
+    }
 
-        if (bom.getDependencies() != null && bom.getDependencies().size() > 0) {
-            final Element dependenciesNode = createElement(bomNode, "dg:dependencies");
-            createDependenciesNode(dependenciesNode, bom.getDependencies());
-        }
-        processExtensions(bomNode, bom);
-        return doc;
+    public String toXmlString() throws GeneratorException {
+        return toXML(this.bom, true);
     }
 }
diff --git a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator12.java b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator12.java
index 573fc5edc..08b2a7382 100644
--- a/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator12.java
+++ b/src/main/java/org/cyclonedx/generators/xml/BomXmlGenerator12.java
@@ -19,15 +19,11 @@
 package org.cyclonedx.generators.xml;
 
 import org.cyclonedx.CycloneDxSchema;
-import org.cyclonedx.model.Attribute;
+import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.Component;
 import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.ParserConfigurationException;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * BomGenerator creates a CycloneDX bill-of-material document from a set of
@@ -44,6 +40,8 @@ public class BomXmlGenerator12 extends AbstractBomXmlGenerator implements BomXml
      * @param bom the BOM to generate
      */
     public BomXmlGenerator12(final Bom bom) {
+        bom.setXmlns(CycloneDxSchema.NS_BOM_12);
+
         this.bom = bom;
     }
 
@@ -61,31 +59,10 @@ public class BomXmlGenerator12 extends AbstractBomXmlGenerator implements BomXml
      * @since 2.0.0
      */
     public Document generate() throws ParserConfigurationException {
-        final DocumentBuilder docBuilder = buildSecureDocumentBuilder();
-        this.doc = docBuilder.newDocument();
-        doc.setXmlStandalone(true);
-
-        final List<Attribute> attributes = new ArrayList<>();
-        attributes.add(new Attribute("xmlns", NS_BOM_12));
-        attributes.add(new Attribute("version", String.valueOf(bom.getVersion())));
-
-        // Create root <bom> node
-        final Element bomNode = createRootElement("bom", null, attributes.toArray(new Attribute[0]));
-        if (bom.getSerialNumber() != null) {
-            bomNode.setAttribute("serialNumber", bom.getSerialNumber());
-        }
-
-        createMetadataNode(bomNode, bom.getMetadata());
-
-        final Element componentsNode = createElement(bomNode, "components");
-        createComponentsNode(componentsNode, bom.getComponents());
-        createExternalReferencesNode(bomNode, bom.getExternalReferences());
+        return generateDocument(this.bom);
+    }
 
-        if (bom.getDependencies() != null && bom.getDependencies().size() > 0) {
-            final Element dependenciesNode = createElement(bomNode, "dependencies");
-            createDependenciesNode(dependenciesNode, bom.getDependencies());
-        }
-        processExtensions(bomNode, bom);
-        return doc;
+    public String toXmlString() throws GeneratorException {
+        return toXML(this.bom, true);
     }
 }
diff --git a/src/main/java/org/cyclonedx/model/AttachmentText.java b/src/main/java/org/cyclonedx/model/AttachmentText.java
index 02b4c94d8..239e850d7 100644
--- a/src/main/java/org/cyclonedx/model/AttachmentText.java
+++ b/src/main/java/org/cyclonedx/model/AttachmentText.java
@@ -20,21 +20,21 @@ package org.cyclonedx.model;
 
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
+
 @SuppressWarnings("unused")
 public class AttachmentText {
 
-    private String contentType;
+    @JacksonXmlProperty(isAttribute = true)
     private String encoding;
+    @JacksonXmlProperty(localName = "content-type", isAttribute = true)
+    private String contentType;
+    @JacksonXmlText
+    @JsonProperty("content")
     private String text;
 
-    public String getContentType() {
-        return contentType;
-    }
-
-    public void setContentType(String contentType) {
-        this.contentType = contentType;
-    }
-
     public String getEncoding() {
         return encoding;
     }
@@ -43,6 +43,14 @@ public class AttachmentText {
         this.encoding = encoding;
     }
 
+    public String getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
     public String getText() {
         return text;
     }
diff --git a/src/main/java/org/cyclonedx/model/Bom.java b/src/main/java/org/cyclonedx/model/Bom.java
index 3ad0fdbe9..40d9fcd38 100644
--- a/src/main/java/org/cyclonedx/model/Bom.java
+++ b/src/main/java/org/cyclonedx/model/Bom.java
@@ -22,16 +22,39 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+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 com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import org.cyclonedx.util.DependencyDeserializer;
+
 @SuppressWarnings("unused")
+@JacksonXmlRootElement(localName = "bom")
+@JsonInclude(Include.NON_NULL)
+@JsonPropertyOrder({"bomFormat", "specVersion", "metadata", "components", "externalReferences", "dependencies", "serialNumber", "version"})
 public class Bom extends ExtensibleElement {
-
+    @JacksonXmlProperty(isAttribute = true)
+    private String xmlns;
+    @VersionFilter(versions = {"1.2"})
     private Metadata metadata;
+    @VersionFilter(versions = {"1.0", "1.1", "1.2"})
     private List<Component> components;
+    @VersionFilter(versions = {"1.1", "1.2"})
     private List<Dependency> dependencies;
     private List<ExternalReference> externalReferences;
+    @JacksonXmlProperty(isAttribute = true)
     private int version = 1;
+    @JacksonXmlProperty(isAttribute = true)
     private String serialNumber;
+    @JsonOnly
     private String specVersion;
+    @JsonOnly
+    private String bomFormat;
 
     public Metadata getMetadata() {
         return metadata;
@@ -41,6 +64,8 @@ public class Bom extends ExtensibleElement {
         this.metadata = metadata;
     }
 
+    @JacksonXmlElementWrapper(localName = "components")
+    @JacksonXmlProperty(localName = "component")
     public List<Component> getComponents() {
         return components;
     }
@@ -56,6 +81,8 @@ public class Bom extends ExtensibleElement {
         components.add(component);
     }
 
+    @JacksonXmlElementWrapper(useWrapping = false)
+    @JsonDeserialize(using = DependencyDeserializer.class)
     public List<Dependency> getDependencies() {
         return dependencies;
     }
@@ -102,6 +129,14 @@ public class Bom extends ExtensibleElement {
         this.serialNumber = serialNumber;
     }
 
+    public String getXmlns() {
+        return xmlns;
+    }
+
+    public void setXmlns(String xmlns) {
+        this.xmlns = xmlns;
+    }
+
     /**
      * Returns the CycloneDX spec version of a Bom. The spec version will
      * only be populated when paring a bom via {@link org.cyclonedx.parsers.Parser}.
@@ -112,6 +147,10 @@ public class Bom extends ExtensibleElement {
         return specVersion;
     }
 
+    public String getBomFormat() {
+        return bomFormat;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/src/main/java/org/cyclonedx/model/Component.java b/src/main/java/org/cyclonedx/model/Component.java
index 7a7c927ff..22c1d3dea 100644
--- a/src/main/java/org/cyclonedx/model/Component.java
+++ b/src/main/java/org/cyclonedx/model/Component.java
@@ -18,22 +18,62 @@
  */
 package org.cyclonedx.model;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+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 com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
 import com.github.packageurl.PackageURL;
+import org.cyclonedx.util.LicenseDeserializer;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
 @SuppressWarnings("unused")
+@JacksonXmlRootElement(localName = "component")
+@JsonInclude(Include.NON_NULL)
+@JsonPropertyOrder(
+    {"supplier",
+     "author",
+     "publisher",
+     "group",
+     "name",
+     "version",
+     "description",
+     "scope",
+     "hashes",
+     "licenses",
+     "copyright",
+     "cpe",
+     "purl",
+     "swid",
+     "modified",
+     "pedigree",
+     "externalReferences",
+     "components"
+    })
 public class Component extends ExtensibleElement {
 
     public enum Type {
+        @JsonProperty("application")
         APPLICATION("application"),
+        @JsonProperty("framework")
         FRAMEWORK("framework"),
+        @JsonProperty("library")
         LIBRARY("library"),
+        @JsonProperty("container")
         CONTAINER("container"),
+        @JsonProperty("operating-system")
         OPERATING_SYSTEM("operating-system"),
+        @JsonProperty("device")
         DEVICE("device"),
+        @JsonProperty("firmware")
         FIRMWARE("firmware"),
+        @JsonProperty("file")
         FILE("file");
 
         private final String name;
@@ -48,8 +88,11 @@ public class Component extends ExtensibleElement {
     }
 
     public enum Scope {
+        @JsonProperty("required")
         REQUIRED("required"),
+        @JsonProperty("optional")
         OPTIONAL("optional"),
+        @JsonProperty("excluded")
         EXCLUDED("excluded");
 
         private final String name;
@@ -63,10 +106,16 @@ public class Component extends ExtensibleElement {
         }
     }
 
+    @JacksonXmlProperty(isAttribute = true, localName = "bom-ref")
+    @JsonProperty("bom-ref")
     private String bomRef;
+    @JacksonXmlProperty(isAttribute = true)
     private String mimeType;
+    @VersionFilter(versions = {"1.2"})
     private OrganizationalEntity supplier;
+    @VersionFilter(versions = {"1.2"})
     private String author;
+    @VersionFilter(versions = {"1.1", "1.2"})
     private String publisher;
     private String group;
     private String name;
@@ -74,15 +123,17 @@ public class Component extends ExtensibleElement {
     private String description;
     private Scope scope;
     private List<Hash> hashes;
-    private LicenseChoice licenseChoice;
+    private LicenseChoice license;
     private String copyright;
     private String cpe;
     private String purl;
+    @VersionFilter(versions = {"1.2"})
     private Swid swid;
-    private boolean modified;
+    private Boolean modified;
     private Pedigree pedigree;
     private List<ExternalReference> externalReferences;
     private List<Component> components;
+    @JacksonXmlProperty(isAttribute = true)
     private Type type;
 
     public String getBomRef() {
@@ -165,6 +216,8 @@ public class Component extends ExtensibleElement {
         this.scope = scope;
     }
 
+    @JacksonXmlElementWrapper(localName = "hashes")
+    @JacksonXmlProperty(localName = "hash")
     public List<Hash> getHashes() {
         return hashes;
     }
@@ -180,12 +233,15 @@ public class Component extends ExtensibleElement {
         this.hashes.add(hash);
     }
 
+    @JacksonXmlProperty(localName = "licenses")
+    @JsonProperty("licenses")
+    @JsonDeserialize(using = LicenseDeserializer.class)
     public LicenseChoice getLicenseChoice() {
-        return licenseChoice;
+        return license;
     }
 
     public void setLicenseChoice(LicenseChoice licenseChoice) {
-        this.licenseChoice = licenseChoice;
+        this.license = licenseChoice;
     }
 
     public String getCopyright() {
@@ -234,11 +290,14 @@ public class Component extends ExtensibleElement {
         this.swid = swid;
     }
 
-    public boolean isModified() {
+    @JsonInclude(Include.NON_NULL)
+    public Boolean getModified() { return modified; }
+
+    public Boolean isModified() {
         return modified;
     }
 
-    public void setModified(boolean modified) {
+    public void setModified(Boolean modified) {
         this.modified = modified;
     }
 
@@ -250,6 +309,8 @@ public class Component extends ExtensibleElement {
         this.pedigree = pedigree;
     }
 
+    @JacksonXmlElementWrapper(localName = "externalReferences")
+    @JacksonXmlProperty(localName = "reference")
     public List<ExternalReference> getExternalReferences() {
         return externalReferences;
     }
@@ -265,6 +326,8 @@ public class Component extends ExtensibleElement {
         this.externalReferences = externalReferences;
     }
 
+    @JacksonXmlElementWrapper(localName = "components")
+    @JacksonXmlProperty(localName = "component")
     public List<Component> getComponents() {
         return components;
     }
@@ -290,7 +353,7 @@ public class Component extends ExtensibleElement {
 
     @Override
     public int hashCode() {
-        return Objects.hash(author, publisher, group, name, version, description, scope, hashes, licenseChoice, copyright, cpe, purl, swid, modified, components, type);
+        return Objects.hash(author, publisher, group, name, version, description, scope, hashes, license, copyright, cpe, purl, swid, modified, components, type);
     }
 
     @Override
@@ -308,7 +371,7 @@ public class Component extends ExtensibleElement {
                 Objects.equals(description, component.description) &&
                 Objects.equals(scope, component.scope) &&
                 Objects.equals(hashes, component.hashes) &&
-                Objects.equals(licenseChoice, component.licenseChoice) &&
+                Objects.equals(license, component.license) &&
                 Objects.equals(copyright, component.copyright) &&
                 Objects.equals(cpe, component.cpe) &&
                 Objects.equals(purl, component.purl) &&
diff --git a/src/main/java/org/cyclonedx/model/Dependency.java b/src/main/java/org/cyclonedx/model/Dependency.java
index dc7da8311..242c85f29 100644
--- a/src/main/java/org/cyclonedx/model/Dependency.java
+++ b/src/main/java/org/cyclonedx/model/Dependency.java
@@ -18,16 +18,23 @@
  */
 package org.cyclonedx.model;
 
-import com.alibaba.fastjson.annotation.JSONField;
-import org.cyclonedx.converters.DependencyDeserializer;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+@JsonInclude(Include.NON_NULL)
 public class Dependency {
 
+    @JacksonXmlProperty(isAttribute = true)
     private String ref;
-    @JSONField(name = "dependsOn", deserializeUsing = DependencyDeserializer.class)
+    @JsonProperty("dependsOn")
+    @JacksonXmlElementWrapper(useWrapping = false)
+    @JacksonXmlProperty(localName = "dependency")
     private List<Dependency> dependencies;
 
     public Dependency(final String ref) {
diff --git a/src/main/java/org/cyclonedx/model/ExtensibleElement.java b/src/main/java/org/cyclonedx/model/ExtensibleElement.java
index 75d730f60..fa5a53ea7 100644
--- a/src/main/java/org/cyclonedx/model/ExtensibleElement.java
+++ b/src/main/java/org/cyclonedx/model/ExtensibleElement.java
@@ -19,12 +19,29 @@
 package org.cyclonedx.model;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import org.cyclonedx.util.ExtensibleTypesSerializer;
+import org.cyclonedx.util.ExtensionDeserializer;
+
+@JsonInclude(Include.NON_NULL)
 public abstract class ExtensibleElement {
 
+    private Map<String, Extension> extensions;
+
     private List<ExtensibleType> extensibleTypes;
 
+    @JsonSerialize(using = ExtensibleTypesSerializer.class)
+    @JacksonXmlElementWrapper(useWrapping = false)
     public List<ExtensibleType> getExtensibleTypes() {
         return extensibleTypes;
     }
@@ -39,4 +56,22 @@ public abstract class ExtensibleElement {
         }
         this.extensibleTypes.add(extensibleType);
     }
+
+    @JsonAnyGetter
+    public Map<String, Extension> getExtensions() {
+        return extensions;
+    }
+
+    public void setExtensions(final Map<String, Extension> extensions) {
+        this.extensions = extensions;
+    }
+
+    @JsonAnySetter
+    @JsonDeserialize(contentUsing = ExtensionDeserializer.class)
+    public void add(final String key, final Extension value) {
+        if (this.extensions == null) {
+            this.extensions = new HashMap<>();
+        }
+        extensions.put(key, value);
+    }
 }
diff --git a/src/main/java/org/cyclonedx/model/ExtensibleType.java b/src/main/java/org/cyclonedx/model/ExtensibleType.java
index 9ca89a729..7bf3c03f8 100644
--- a/src/main/java/org/cyclonedx/model/ExtensibleType.java
+++ b/src/main/java/org/cyclonedx/model/ExtensibleType.java
@@ -27,7 +27,11 @@ public class ExtensibleType extends ExtensibleElement {
     private List<Attribute> attributes;
     private String value;
 
-    public ExtensibleType(final String namespace, final String name, final List<Attribute> attributes, final String value) {
+    public ExtensibleType(
+        final String namespace,
+        final String name,
+        final List<Attribute> attributes,
+        final String value) {
         this.namespace = namespace;
         this.name = name;
         this.attributes = attributes;
diff --git a/src/main/java/org/cyclonedx/model/Extension.java b/src/main/java/org/cyclonedx/model/Extension.java
new file mode 100644
index 000000000..6aa35f267
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/Extension.java
@@ -0,0 +1,96 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.cyclonedx.util.ExtensionSerializer;
+
+@JsonSerialize(using = ExtensionSerializer.class)
+public class Extension
+{
+  public Extension() {
+  }
+
+  public Extension(final ExtensionType type, final List<ExtensibleType> extensions) {
+    this.extensionType = type;
+    this.extensions = extensions;
+  }
+
+  public enum ExtensionType {
+    VULNERABILITIES("vulnerabilities");
+
+    private final String type;
+
+    public String getTypeName() {
+      return this.type;
+    }
+
+    ExtensionType(String type) {
+      this.type = type;
+    }
+  }
+
+  private ExtensionType extensionType;
+  private String namespaceURI;
+  private String prefix;
+
+  public ExtensionType getExtensionType() {
+    return extensionType;
+  }
+
+  public void setExtensionType(final ExtensionType extensionType) {
+    this.extensionType = extensionType;
+  }
+
+  public void setNamespaceURI(final String namespaceURI) {
+    this.namespaceURI = namespaceURI;
+  }
+
+  public void setPrefix(final String prefix) {
+    this.prefix = prefix;
+  }
+
+  private List<ExtensibleType> extensions;
+
+  public List<ExtensibleType> getExtensions() {
+    return extensions;
+  }
+
+  public void setExtensions(final List<ExtensibleType> extensions) {
+    this.extensions = extensions;
+  }
+
+  public void addExtension(ExtensibleType extensibleType) {
+    if (this.extensions == null) {
+      this.extensions = new ArrayList<>();
+    }
+    this.extensions.add(extensibleType);
+  }
+
+  public String getPrefix() {
+    return prefix;
+  }
+
+  public String getNamespaceURI() {
+    return namespaceURI;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/ExternalReference.java b/src/main/java/org/cyclonedx/model/ExternalReference.java
index 32d833cb5..fe1685e55 100644
--- a/src/main/java/org/cyclonedx/model/ExternalReference.java
+++ b/src/main/java/org/cyclonedx/model/ExternalReference.java
@@ -20,27 +20,51 @@ package org.cyclonedx.model;
 
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import org.cyclonedx.util.ExternalReferenceSerializer;
+
 @SuppressWarnings("unused")
+@JsonInclude(Include.NON_NULL)
+@JsonSerialize(using = ExternalReferenceSerializer.class)
 public class ExternalReference {
 
     public enum Type {
+        @JsonProperty("vcs")
         VCS("vcs"),
+        @JsonProperty("issue-tracker")
         ISSUE_TRACKER("issue-tracker"),
+        @JsonProperty("website")
         WEBSITE("website"),
+        @JsonProperty("advisories")
         ADVISORIES("advisories"),
+        @JsonProperty("bom")
         BOM("bom"),
+        @JsonProperty("mailing-list")
         MAILING_LIST("mailing-list"),
+        @JsonProperty("social")
         SOCIAL("social"),
+        @JsonProperty("chat")
         CHAT("chat"),
+        @JsonProperty("documentation")
         DOCUMENTATION("documentation"),
+        @JsonProperty("support")
         SUPPORT("support"),
+        @JsonProperty("distribution")
         DISTRIBUTION("distribution"),
+        @JsonProperty("license")
         LICENSE("license"),
+        @JsonProperty("build-meta")
         BUILD_META("build-meta"),
+        @JsonProperty("build-system")
         BUILD_SYSTEM("build-system"),
+        @JsonProperty("other")
         OTHER("other");
 
-        private String name;
+        private final String name;
 
         public String getTypeName() {
             return this.name;
@@ -52,6 +76,7 @@ public class ExternalReference {
     }
 
     private String url;
+    @JacksonXmlProperty(localName = "type", isAttribute = true)
     private Type type;
     private String comment;
 
diff --git a/src/main/java/org/cyclonedx/model/Hash.java b/src/main/java/org/cyclonedx/model/Hash.java
index 6d3623d02..53686948a 100644
--- a/src/main/java/org/cyclonedx/model/Hash.java
+++ b/src/main/java/org/cyclonedx/model/Hash.java
@@ -18,7 +18,10 @@
  */
 package org.cyclonedx.model;
 
-import com.alibaba.fastjson.annotation.JSONField;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
+
 import java.util.Objects;
 
 @SuppressWarnings("unused")
@@ -39,6 +42,7 @@ public class Hash {
         BLAKE3("BLAKE3");
 
         private String spec;
+
         Algorithm(String spec) {
             this.spec = spec;
         }
@@ -48,10 +52,12 @@ public class Hash {
         }
     }
 
-    @JSONField(name = "alg")
+    @JacksonXmlProperty(localName = "alg", isAttribute = true)
+    @JsonProperty("alg")
     private String algorithm;
 
-    @JSONField(name = "content")
+    @JacksonXmlText
+    @JsonProperty("content")
     private String value;
 
     public Hash() { }
diff --git a/src/main/java/org/cyclonedx/model/IdentifiableActionType.java b/src/main/java/org/cyclonedx/model/IdentifiableActionType.java
index a14bd6b4f..48831a359 100644
--- a/src/main/java/org/cyclonedx/model/IdentifiableActionType.java
+++ b/src/main/java/org/cyclonedx/model/IdentifiableActionType.java
@@ -21,9 +21,13 @@ package org.cyclonedx.model;
 import java.util.Date;
 import java.util.Objects;
 
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.cyclonedx.util.CustomDateSerializer;
+
 @SuppressWarnings("unused")
 public class IdentifiableActionType extends ExtensibleElement {
 
+    @JsonSerialize(using = CustomDateSerializer.class)
     private Date timestamp;
     private String name;
     private String email;
diff --git a/src/main/java/org/cyclonedx/CycloneDxMediaType.java b/src/main/java/org/cyclonedx/model/JsonOnly.java
similarity index 53%
rename from src/main/java/org/cyclonedx/CycloneDxMediaType.java
rename to src/main/java/org/cyclonedx/model/JsonOnly.java
index 7704a5424..a26bf5cc2 100644
--- a/src/main/java/org/cyclonedx/CycloneDxMediaType.java
+++ b/src/main/java/org/cyclonedx/model/JsonOnly.java
@@ -16,24 +16,14 @@
  * SPDX-License-Identifier: Apache-2.0
  * Copyright (c) Steve Springett. All Rights Reserved.
  */
-package org.cyclonedx;
+package org.cyclonedx.model;
 
-public class CycloneDxMediaType {
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
-    private CycloneDxMediaType() { }
-
-    /**
-     * Official CycloneDX XML media type assigned by IANA.
-     *
-     * https://www.iana.org/assignments/media-types/application/vnd.cyclonedx+xml
-     */
-    public static final String APPLICATION_CYCLONEDX_XML = "application/vnd.cyclonedx+xml";
-
-    /**
-     * Official CycloneDX JSON media type assigned by IANA.
-     *
-     * https://www.iana.org/assignments/media-types/application/vnd.cyclonedx+json
-     */
-    public static final String APPLICATION_CYCLONEDX_JSON = "application/vnd.cyclonedx+json";
-
-}
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface JsonOnly
+{}
diff --git a/src/main/java/org/cyclonedx/model/License.java b/src/main/java/org/cyclonedx/model/License.java
index 4bb304ec5..62b415853 100644
--- a/src/main/java/org/cyclonedx/model/License.java
+++ b/src/main/java/org/cyclonedx/model/License.java
@@ -20,13 +20,28 @@ package org.cyclonedx.model;
 
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
 @SuppressWarnings("unused")
+@JsonInclude(Include.NON_NULL)
+@JsonPropertyOrder({"id", "name", "text", "url"})
+@JsonRootName("license")
 public class License extends ExtensibleElement {
 
+    @JacksonXmlProperty(localName = "id")
+    @JsonProperty("id")
     private String id;
     private String name;
-    private String url;
+
+    @JacksonXmlProperty(localName = "text")
+    @JsonProperty("text")
     private AttachmentText attachmentText;
+    private String url;
 
     public String getId() {
         return id;
diff --git a/src/main/java/org/cyclonedx/model/LicenseChoice.java b/src/main/java/org/cyclonedx/model/LicenseChoice.java
index ac37aa992..f6b7a411e 100644
--- a/src/main/java/org/cyclonedx/model/LicenseChoice.java
+++ b/src/main/java/org/cyclonedx/model/LicenseChoice.java
@@ -22,25 +22,37 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+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.util.LicenseDeserializer;
+
+@JsonInclude(Include.NON_NULL)
 public class LicenseChoice {
 
-    private List<License> licenses;
+    private List<License> license;
     private String expression;
 
+    @JacksonXmlProperty(localName = "license")
+    @JacksonXmlElementWrapper(useWrapping = false)
     public List<License> getLicenses() {
-        return licenses;
+        return license;
     }
 
     public void setLicenses(List<License> licenses) {
-        this.licenses = licenses;
+        this.license = licenses;
         this.expression = null;
     }
 
     public void addLicense(License license) {
-        if (this.licenses == null) {
-            this.licenses = new ArrayList<>();
+        if (this.license == null) {
+            this.license = new ArrayList<>();
         }
-        this.licenses.add(license);
+        this.license.add(license);
         this.expression = null;
     }
 
@@ -50,7 +62,7 @@ public class LicenseChoice {
 
     public void setExpression(String expression) {
         this.expression = expression;
-        this.licenses = null;
+        this.license = null;
     }
 
     @Override
@@ -58,12 +70,12 @@ public class LicenseChoice {
         if (this == o) return true;
         if (!(o instanceof LicenseChoice)) return false;
         LicenseChoice that = (LicenseChoice) o;
-        return Objects.equals(licenses, that.licenses) &&
+        return Objects.equals(license, that.license) &&
                 Objects.equals(expression, that.expression);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(licenses, expression);
+        return Objects.hash(license, expression);
     }
 }
diff --git a/src/main/java/org/cyclonedx/model/Metadata.java b/src/main/java/org/cyclonedx/model/Metadata.java
index 5b66a2f4c..16ea1940e 100644
--- a/src/main/java/org/cyclonedx/model/Metadata.java
+++ b/src/main/java/org/cyclonedx/model/Metadata.java
@@ -23,8 +23,16 @@ import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import org.cyclonedx.util.CustomDateSerializer;
+
+@JsonPropertyOrder({"timestamp", "tools", "authors"})
 public class Metadata extends ExtensibleElement {
 
+    @JsonSerialize(using = CustomDateSerializer.class)
     private Date timestamp = new Date();
     private List<Tool> tools;
     private List<OrganizationalContact> authors;
@@ -40,6 +48,8 @@ public class Metadata extends ExtensibleElement {
         this.timestamp = timestamp;
     }
 
+    @JacksonXmlElementWrapper(localName = "tools")
+    @JacksonXmlProperty(localName = "tool")
     public List<Tool> getTools() {
         return tools;
     }
@@ -55,6 +65,8 @@ public class Metadata extends ExtensibleElement {
         this.tools.add(tool);
     }
 
+    @JacksonXmlElementWrapper(localName = "authors")
+    @JacksonXmlProperty(localName = "author")
     public List<OrganizationalContact> getAuthors() {
         return authors;
     }
diff --git a/src/main/java/org/cyclonedx/model/OrganizationalEntity.java b/src/main/java/org/cyclonedx/model/OrganizationalEntity.java
index 48441bd4c..d025e4354 100644
--- a/src/main/java/org/cyclonedx/model/OrganizationalEntity.java
+++ b/src/main/java/org/cyclonedx/model/OrganizationalEntity.java
@@ -18,18 +18,23 @@
  */
 package org.cyclonedx.model;
 
-import com.alibaba.fastjson.annotation.JSONField;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
+@JsonPropertyOrder({"name", "url", "contact"})
 public class OrganizationalEntity extends ExtensibleElement {
 
     private String name;
-    private String url;
-    @JSONField(name = "contact")
-    private List<OrganizationalContact> contacts;
+
+    private List<String> url;
+    @JsonProperty("contact")
+    private List<OrganizationalContact> contact;
 
     public String getName() {
         return name;
@@ -39,27 +44,33 @@ public class OrganizationalEntity extends ExtensibleElement {
         this.name = name;
     }
 
-    public String getUrl() {
+    @JacksonXmlElementWrapper(useWrapping = false)
+    @JacksonXmlProperty(localName = "url")
+    @JsonProperty("url")
+    public List<String> getUrls() {
         return url;
     }
 
-    public void setUrl(String url) {
+    public void setUrls(List<String> url) {
         this.url = url;
     }
 
+    @JacksonXmlElementWrapper(useWrapping = false)
+    @JacksonXmlProperty(localName = "contact")
+    @JsonProperty("contact")
     public List<OrganizationalContact> getContacts() {
-        return contacts;
+        return contact;
     }
 
     public void setContacts(List<OrganizationalContact> contacts) {
-        this.contacts = contacts;
+        this.contact = contacts;
     }
 
     public void addContact(OrganizationalContact contact) {
-        if (this.contacts == null) {
-            this.contacts = new ArrayList<>();
+        if (this.contact == null) {
+            this.contact = new ArrayList<>();
         }
-        this.contacts.add(contact);
+        this.contact.add(contact);
     }
 
     @Override
@@ -69,11 +80,11 @@ public class OrganizationalEntity extends ExtensibleElement {
         OrganizationalEntity that = (OrganizationalEntity) o;
         return Objects.equals(name, that.name) &&
                 Objects.equals(url, that.url) &&
-                Objects.equals(contacts, that.contacts);
+                Objects.equals(contact, that.contact);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(name, url, contacts);
+        return Objects.hash(name, url, contact);
     }
 }
diff --git a/src/main/java/org/cyclonedx/model/Pedigree.java b/src/main/java/org/cyclonedx/model/Pedigree.java
index b29d6a94c..dc9534185 100644
--- a/src/main/java/org/cyclonedx/model/Pedigree.java
+++ b/src/main/java/org/cyclonedx/model/Pedigree.java
@@ -21,7 +21,12 @@ package org.cyclonedx.model;
 import java.util.List;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
 @SuppressWarnings("unused")
+@JsonPropertyOrder({"ancestors", "commits", "notes"})
 public class Pedigree extends ExtensibleElement {
 
     private List<Component> ancestors;
@@ -30,6 +35,8 @@ public class Pedigree extends ExtensibleElement {
     private List<Commit> commits;
     private String notes;
 
+    @JacksonXmlElementWrapper(localName = "ancestors")
+    @JacksonXmlProperty(localName = "component")
     public List<Component> getAncestors() {
         return ancestors;
     }
@@ -54,6 +61,8 @@ public class Pedigree extends ExtensibleElement {
         this.variants = variants;
     }
 
+    @JacksonXmlElementWrapper(localName = "commits")
+    @JacksonXmlProperty(localName = "commit")
     public List<Commit> getCommits() {
         return commits;
     }
diff --git a/src/main/java/org/cyclonedx/model/Swid.java b/src/main/java/org/cyclonedx/model/Swid.java
index 0788deb27..21d8f38a3 100644
--- a/src/main/java/org/cyclonedx/model/Swid.java
+++ b/src/main/java/org/cyclonedx/model/Swid.java
@@ -20,13 +20,23 @@ package org.cyclonedx.model;
 
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
 public class Swid {
 
+    @JacksonXmlProperty(isAttribute = true)
     private String tagId;
+    @JacksonXmlProperty(isAttribute = true)
     private String name;
+    @JacksonXmlProperty(isAttribute = true)
     private String version;
+    @JacksonXmlProperty(isAttribute = true)
     private int tagVersion;
+    @JacksonXmlProperty(isAttribute = true)
     private boolean patch;
+    @JacksonXmlProperty(localName = "text")
+    @JsonProperty("text")
     private AttachmentText attachmentText;
 
     public String getTagId() {
diff --git a/src/main/java/org/cyclonedx/model/Tool.java b/src/main/java/org/cyclonedx/model/Tool.java
index 31a2b1414..4c7e52570 100644
--- a/src/main/java/org/cyclonedx/model/Tool.java
+++ b/src/main/java/org/cyclonedx/model/Tool.java
@@ -21,6 +21,11 @@ package org.cyclonedx.model;
 import java.util.List;
 import java.util.Objects;
 
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+@JsonPropertyOrder({"vendor", "name", "version", "hashes"})
 public class Tool extends ExtensibleElement {
 
     private String vendor;
@@ -52,6 +57,8 @@ public class Tool extends ExtensibleElement {
         this.version = version;
     }
 
+    @JacksonXmlElementWrapper(localName = "hashes")
+    @JacksonXmlProperty(localName = "hash")
     public List<Hash> getHashes() {
         return hashes;
     }
diff --git a/src/main/java/org/cyclonedx/model/VersionFilter.java b/src/main/java/org/cyclonedx/model/VersionFilter.java
new file mode 100644
index 000000000..3841214b7
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/VersionFilter.java
@@ -0,0 +1,13 @@
+package org.cyclonedx.model;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface VersionFilter
+{
+  String[] versions();
+}
diff --git a/src/main/java/org/cyclonedx/model/vulnerability/Rating.java b/src/main/java/org/cyclonedx/model/vulnerability/Rating.java
new file mode 100644
index 000000000..60a85a09e
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/vulnerability/Rating.java
@@ -0,0 +1,66 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.model.vulnerability;
+
+import org.cyclonedx.model.vulnerability.Vulnerability10.Score;
+import org.cyclonedx.model.vulnerability.Vulnerability10.ScoreSource;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Severity;
+
+public class Rating
+{
+  private Score score;
+
+  private Severity severity;
+
+  private ScoreSource method;
+
+  private String vector;
+
+  public Score getScore() {
+    return score;
+  }
+
+  public void setScore(final Score score) {
+    this.score = score;
+  }
+
+  public Severity getSeverity() {
+    return severity;
+  }
+
+  public void setSeverity(final Severity severity) {
+    this.severity = severity;
+  }
+
+  public ScoreSource getMethod() {
+    return method;
+  }
+
+  public void setMethod(final ScoreSource method) {
+    this.method = method;
+  }
+
+  public String getVector() {
+    return vector;
+  }
+
+  public void setVector(final String vector) {
+    this.vector = vector;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
new file mode 100644
index 000000000..da67321e0
--- /dev/null
+++ b/src/main/java/org/cyclonedx/model/vulnerability/Vulnerability10.java
@@ -0,0 +1,291 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.model.vulnerability;
+
+import java.net.URL;
+import java.util.List;
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import org.cyclonedx.model.ExtensibleType;
+
+@JsonRootName(Vulnerability10.NAME)
+public class Vulnerability10
+    extends ExtensibleType implements Serializable
+{
+  public static final String NAMESPACE_URI = "http://cyclonedx.org/schema/ext/vulnerability/1.0";
+  public static final String PREFIX = "v";
+  public static final String NAME = "vulnerability";
+  public static final String ID = "id";
+  public static final String SOURCE = "source";
+  public static final String URL = "url";
+  public static final String VULNERABILITIES = "vulnerabilities";
+  public static final String RATINGS = "ratings";
+  public static final String RATING = "rating";
+  public static final String REF = "ref";
+  public static final String CWES = "cwes";
+  public static final String CWE = "cwe";
+  public static final String DESCRIPTION = "description";
+  public static final String ADVISORIES = "advisories";
+  public static final String ADVISORY = "advisory";
+  public static final String RECOMMENDATIONS = "recommendations";
+  public static final String RECOMMENDATION = "recommendation";
+  public static final String SCORE = "score";
+  public static final String BASE = "base";
+  public static final String IMPACT = "impact";
+  public static final String EXPLOITABILITY = "exploitability";
+  public static final String SEVERITY = "severity";
+  public static final String METHOD = "method";
+  public static final String VECTOR = "vector";
+
+  public Vulnerability10() {
+    super(PREFIX, NAME);
+  }
+
+  public Vulnerability10(final String namespace, final String name) {
+    super(namespace, name);
+  }
+
+  public enum Severity {
+    NONE("None"),
+    LOW("Low"),
+    MEDIUM("Medium"),
+    HIGH("High"),
+    CRITICAL("Critical"),
+    UNKNOWN("Unknown");
+
+    private final String name;
+
+    public String getSeverityName() {
+      return this.name;
+    }
+
+    Severity(String name) {
+      this.name = name;
+    }
+
+    public static Severity fromString(String text) {
+      for (Severity s : Severity.values()) {
+        if (s.name.equals(text)) {
+          return s;
+        }
+      }
+      return null;
+    }
+  }
+
+  public enum ScoreSource {
+    CVSSv3("CVSSv3"),
+    CVSSv2("CVSSv2"),
+    OWASP("OWASP Risk"),
+    FAIR("Open FAIR"),
+    OTHER("Other");
+
+    private final String name;
+
+    public String getScoreSourceName() {
+      return this.name;
+    }
+
+    ScoreSource(String name) {
+      this.name = name;
+    }
+
+    public static ScoreSource fromString(String text) {
+      for (ScoreSource s : ScoreSource.values()) {
+        if (s.name.equals(text)) {
+          return s;
+        }
+      }
+      return null;
+    }
+  }
+
+  private String id;
+  @JsonProperty(Vulnerability10.REF)
+  private String ref;
+  private Source source;
+  private List<Rating> ratings;
+  private List<Cwe> cwes;
+  private String description;
+  private List<Recommendation> recommendations;
+  private List<Advisory> advisories;
+
+  public List<Advisory> getAdvisories() {
+    return advisories;
+  }
+
+  public void setAdvisories(final List<Advisory> advisories) {
+    this.advisories = advisories;
+  }
+
+  public List<Recommendation> getRecommendations() {
+    return recommendations;
+  }
+
+  public void setRecommendations(final List<Recommendation> recommendations) {
+    this.recommendations = recommendations;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(final String description) {
+    this.description = description;
+  }
+
+  public List<Cwe> getCwes() {
+    return cwes;
+  }
+
+  public void setCwes(final List<Cwe> cwes) {
+    this.cwes = cwes;
+  }
+
+  @JacksonXmlElementWrapper(localName = RATINGS)
+  @JacksonXmlProperty(localName = RATING)
+  public List<Rating> getRatings() {
+    return ratings;
+  }
+
+  public void setRatings(final List<Rating> ratings) {
+    this.ratings = ratings;
+  }
+
+  public Source getSource() {
+    return source;
+  }
+
+  public void setSource(final Source source) {
+    this.source = source;
+  }
+
+  public String getRef() {
+    return ref;
+  }
+
+  public void setRef(final String ref) {
+    this.ref = ref;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(final String id) {
+    this.id = id;
+  }
+
+  public static class Source {
+    @JacksonXmlProperty(localName = "name", isAttribute = true)
+    private String name;
+    private URL url;
+
+    public URL getUrl() {
+      return url;
+    }
+
+    public void setUrl(final URL url) {
+      this.url = url;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(final String name) {
+      this.name = name;
+    }
+  }
+
+  public static class Score {
+    private Double base;
+    private Double impact;
+    private Double exploitability;
+
+    public Double getBase() {
+      return base;
+    }
+
+    public void setBase(final Double base) {
+      this.base = base;
+    }
+
+    public Double getImpact() {
+      return impact;
+    }
+
+    public void setImpact(final Double impact) {
+      this.impact = impact;
+    }
+
+    public Double getExploitability() {
+      return exploitability;
+    }
+
+    public void setExploitability(final Double exploitability) {
+      this.exploitability = exploitability;
+    }
+  }
+
+  public static class Cwe {
+    private Integer text;
+
+    public Integer getText() {
+      return text;
+    }
+
+    public void setText(final Integer text) {
+      this.text = text;
+    }
+  }
+
+  public static class Advisory {
+    private String text;
+
+    public String getText() {
+      return text;
+    }
+
+    public void setText(final String text) {
+      this.text = text;
+    }
+  }
+
+  public static class Recommendation implements Comparable<Recommendation> {
+    private String text;
+
+    public String getText() {
+      return text;
+    }
+
+    public void setText(final String text) {
+      this.text = text;
+    }
+
+    @Override
+    public int compareTo(final Recommendation r) {
+      return text.compareTo(r.getText());
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/parsers/JsonParser.java b/src/main/java/org/cyclonedx/parsers/JsonParser.java
index 7a3154c65..6b7fcf088 100644
--- a/src/main/java/org/cyclonedx/parsers/JsonParser.java
+++ b/src/main/java/org/cyclonedx/parsers/JsonParser.java
@@ -18,8 +18,7 @@
  */
 package org.cyclonedx.parsers;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.Feature;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.cyclonedx.CycloneDxSchema;
@@ -28,7 +27,6 @@ import org.cyclonedx.model.Bom;
 import org.everit.json.schema.ValidationException;
 import org.json.JSONObject;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
@@ -45,13 +43,18 @@ import java.util.List;
 @SuppressWarnings("unused")
 public class JsonParser extends CycloneDxSchema implements Parser {
 
+    private final ObjectMapper mapper;
+
+    public JsonParser() {
+        mapper = new ObjectMapper();
+    }
+
     /**
      * {@inheritDoc}
      */
     public Bom parse(final File file) throws ParseException {
         try {
-            final String jsonString = IOUtils.toString(new FileInputStream(file), StandardCharsets.UTF_8);
-            return JSON.parseObject(jsonString, Bom.class, Feature.SupportNonPublicField);
+            return mapper.readValue(file, Bom.class);
         } catch (IOException e) {
             throw new ParseException("Unable to parse BOM from File", e);
         }
@@ -62,8 +65,8 @@ public class JsonParser extends CycloneDxSchema implements Parser {
      */
     public Bom parse(final byte[] bomBytes) throws ParseException {
         try {
-            return JSON.parseObject(bomBytes, Bom.class, Feature.SupportNonPublicField);
-        } catch (RuntimeException e) {
+            return mapper.readValue(bomBytes, Bom.class);
+        } catch (RuntimeException | IOException e) {
             throw new ParseException("Unable to parse BOM from byte array", e);
         }
     }
@@ -73,7 +76,7 @@ public class JsonParser extends CycloneDxSchema implements Parser {
      */
     public Bom parse(final InputStream inputStream) throws ParseException {
         try {
-            return JSON.parseObject(inputStream, Bom.class, Feature.SupportNonPublicField);
+            return mapper.readValue(inputStream, Bom.class);
         } catch (IOException e) {
             throw new ParseException("Unable to parse BOM from InputStream", e);
         }
@@ -84,8 +87,7 @@ public class JsonParser extends CycloneDxSchema implements Parser {
      */
     public Bom parse(final Reader reader) throws ParseException {
         try {
-            InputStream in = IOUtils.toInputStream(IOUtils.toString(reader), StandardCharsets.UTF_8);
-            return JSON.parseObject(in, Bom.class, Feature.SupportNonPublicField);
+            return mapper.readValue(reader, Bom.class);
         } catch (IOException e) {
             throw new ParseException("Unable to parse BOM from Reader", e);
         }
diff --git a/src/main/java/org/cyclonedx/parsers/XmlParser.java b/src/main/java/org/cyclonedx/parsers/XmlParser.java
index 014c81b81..8028de8f7 100644
--- a/src/main/java/org/cyclonedx/parsers/XmlParser.java
+++ b/src/main/java/org/cyclonedx/parsers/XmlParser.java
@@ -18,39 +18,17 @@
  */
 package org.cyclonedx.parsers;
 
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.XStreamException;
-import com.thoughtworks.xstream.converters.enums.EnumToStringConverter;
-import com.thoughtworks.xstream.converters.extended.ISO8601DateConverter;
-import com.thoughtworks.xstream.io.xml.QNameMap;
-import com.thoughtworks.xstream.io.xml.StaxDriver;
-import com.thoughtworks.xstream.mapper.CannotResolveClassException;
-import com.thoughtworks.xstream.mapper.MapperWrapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
 import org.cyclonedx.CycloneDxSchema;
-import org.cyclonedx.converters.HashConverter;
-import org.cyclonedx.converters.AttachmentTextConverter;
 import org.cyclonedx.exception.ParseException;
 import org.cyclonedx.model.Bom;
-import org.cyclonedx.model.Commit;
-import org.cyclonedx.model.Component;
-import org.cyclonedx.model.Dependency;
-import org.cyclonedx.model.ExternalReference;
-import org.cyclonedx.model.Hash;
-import org.cyclonedx.model.License;
-import org.cyclonedx.model.LicenseChoice;
-import org.cyclonedx.model.Metadata;
-import org.cyclonedx.model.OrganizationalContact;
-import org.cyclonedx.model.OrganizationalEntity;
-import org.cyclonedx.model.Pedigree;
-import org.cyclonedx.model.Swid;
-import org.cyclonedx.model.Tool;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.ErrorHandler;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 import org.xml.sax.SAXParseException;
-import javax.xml.namespace.QName;
 import javax.xml.transform.Source;
 import javax.xml.transform.stream.StreamSource;
 import javax.xml.validation.Schema;
@@ -63,15 +41,12 @@ import javax.xml.xpath.XPathFactory;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
 import java.lang.reflect.Field;
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 
 /**
  * XmlParser is responsible for validating and parsing CycloneDX bill-of-material
@@ -81,6 +56,12 @@ import java.util.Map;
 @SuppressWarnings("unused")
 public class XmlParser extends CycloneDxSchema implements Parser {
 
+    private final ObjectMapper mapper;
+
+    public XmlParser() {
+        mapper = new XmlMapper();
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -88,10 +69,9 @@ public class XmlParser extends CycloneDxSchema implements Parser {
         try {
             final String schemaVersion = identifySchemaVersion(
                     extractAllNamespaceDeclarations(new InputSource(new FileInputStream(file))));
-            final XStream xstream = mapDefaultObjectModel(createXStream());
-            final Bom bom = (Bom) xstream.fromXML(file);
-            return injectSchemaVersion(bom, schemaVersion);
-        } catch (XStreamException | XPathExpressionException | FileNotFoundException e) {
+
+            return injectSchemaVersion(mapper.readValue(file, Bom.class), schemaVersion);
+        } catch (IOException | XPathExpressionException e) {
             throw new ParseException(e);
         }
     }
@@ -103,10 +83,9 @@ public class XmlParser extends CycloneDxSchema implements Parser {
         try {
             final String schemaVersion = identifySchemaVersion(
                     extractAllNamespaceDeclarations(new InputSource(new ByteArrayInputStream(bomBytes))));
-            final XStream xstream = mapDefaultObjectModel(createXStream());
-            final Bom bom = (Bom)xstream.fromXML(new ByteArrayInputStream(bomBytes));
-            return injectSchemaVersion(bom, schemaVersion);
-        } catch (XStreamException | XPathExpressionException e) {
+
+            return injectSchemaVersion(mapper.readValue(bomBytes, Bom.class), schemaVersion);
+        } catch (IOException | XPathExpressionException e) {
             throw new ParseException(e);
         }
     }
@@ -116,9 +95,8 @@ public class XmlParser extends CycloneDxSchema implements Parser {
      */
     public Bom parse(final InputStream inputStream) throws ParseException {
         try {
-            XStream xstream = mapDefaultObjectModel(createXStream());
-            return (Bom) xstream.fromXML(inputStream);
-        } catch (XStreamException e) {
+            return mapper.readValue(inputStream, Bom.class);
+        } catch (IOException e) {
             throw new ParseException(e);
         }
     }
@@ -128,9 +106,8 @@ public class XmlParser extends CycloneDxSchema implements Parser {
      */
     public Bom parse(final Reader reader) throws ParseException {
         try {
-            XStream xstream = mapDefaultObjectModel(createXStream());
-            return (Bom) xstream.fromXML(reader);
-        } catch (XStreamException e) {
+            return mapper.readValue(reader, Bom.class);
+        } catch (IOException e) {
             throw new ParseException(e);
         }
     }
@@ -318,134 +295,4 @@ public class XmlParser extends CycloneDxSchema implements Parser {
         final XPathExpression xPathExpression = xPath.compile("//namespace::*");
         return (NodeList) xPathExpression.evaluate(in, XPathConstants.NODESET);
     }
-
-    private Map<String, Component.Type> getComponentTypeMapping() {
-        final Map<String, Component.Type> map = new HashMap<>();
-        map.put(Component.Type.APPLICATION.getTypeName(), Component.Type.APPLICATION);
-        map.put(Component.Type.FRAMEWORK.getTypeName(), Component.Type.FRAMEWORK);
-        map.put(Component.Type.LIBRARY.getTypeName(), Component.Type.LIBRARY);
-        map.put(Component.Type.CONTAINER.getTypeName(), Component.Type.CONTAINER);
-        map.put(Component.Type.OPERATING_SYSTEM.getTypeName(), Component.Type.OPERATING_SYSTEM);
-        map.put(Component.Type.FIRMWARE.getTypeName(), Component.Type.FIRMWARE);
-        map.put(Component.Type.DEVICE.getTypeName(), Component.Type.DEVICE);
-        map.put(Component.Type.FILE.getTypeName(), Component.Type.FILE);
-        return map;
-    }
-
-    private Map<String, Component.Scope> getComponentScopeMapping() {
-        final Map<String, Component.Scope> map = new HashMap<>();
-        map.put(Component.Scope.REQUIRED.getScopeName(), Component.Scope.REQUIRED);
-        map.put(Component.Scope.EXCLUDED.getScopeName(), Component.Scope.EXCLUDED);
-        map.put(Component.Scope.OPTIONAL.getScopeName(), Component.Scope.OPTIONAL);
-        return map;
-    }
-
-    private Map<String, ExternalReference.Type> getExternalReferenceTypeMapping() {
-        final Map<String, ExternalReference.Type> map = new HashMap<>();
-        map.put(ExternalReference.Type.VCS.getTypeName(), ExternalReference.Type.VCS);
-        map.put(ExternalReference.Type.ISSUE_TRACKER.getTypeName(), ExternalReference.Type.ISSUE_TRACKER);
-        map.put(ExternalReference.Type.WEBSITE.getTypeName(), ExternalReference.Type.WEBSITE);
-        map.put(ExternalReference.Type.ADVISORIES.getTypeName(), ExternalReference.Type.ADVISORIES);
-        map.put(ExternalReference.Type.BOM.getTypeName(), ExternalReference.Type.BOM);
-        map.put(ExternalReference.Type.MAILING_LIST.getTypeName(), ExternalReference.Type.MAILING_LIST);
-        map.put(ExternalReference.Type.SOCIAL.getTypeName(), ExternalReference.Type.SOCIAL);
-        map.put(ExternalReference.Type.CHAT.getTypeName(), ExternalReference.Type.CHAT);
-        map.put(ExternalReference.Type.DOCUMENTATION.getTypeName(), ExternalReference.Type.DOCUMENTATION);
-        map.put(ExternalReference.Type.SUPPORT.getTypeName(), ExternalReference.Type.SUPPORT);
-        map.put(ExternalReference.Type.DISTRIBUTION.getTypeName(), ExternalReference.Type.DISTRIBUTION);
-        map.put(ExternalReference.Type.LICENSE.getTypeName(), ExternalReference.Type.LICENSE);
-        map.put(ExternalReference.Type.BUILD_META.getTypeName(), ExternalReference.Type.BUILD_META);
-        map.put(ExternalReference.Type.BUILD_SYSTEM.getTypeName(), ExternalReference.Type.BUILD_SYSTEM);
-        map.put(ExternalReference.Type.OTHER.getTypeName(), ExternalReference.Type.OTHER);
-        return map;
-    }
-
-    private XStream createXStream() {
-        final QName qname = new QName(CycloneDxSchema.NS_BOM_LATEST);
-        final QNameMap nsm = new QNameMap();
-        nsm.registerMapping(qname, Bom.class);
-        nsm.setDefaultNamespace(CycloneDxSchema.NS_BOM_LATEST);
-        final XStream xstream = new XStream(new StaxDriver(nsm)) {
-            @Override
-            protected MapperWrapper wrapMapper(MapperWrapper next) {
-                return new MapperWrapper(next) {
-                    @Override
-                    public boolean shouldSerializeMember(Class definedIn, String fieldName) {
-                        try {
-                            return definedIn != Object.class || realClass(fieldName) != null;
-                        } catch(CannotResolveClassException e) {
-                            return false;
-                        }
-                    }
-                    @Override
-                    public Class realClass(String elementName) {
-                        try {
-                            return super.realClass(elementName);
-                        } catch(CannotResolveClassException e) {
-                            return null;
-                        }
-                    }
-                };
-            }
-        };
-        XStream.setupDefaultSecurity(xstream);
-        xstream.allowTypesByWildcard(new String[] {
-                "org.cyclonedx.model.**"
-        });
-        return xstream;
-    }
-
-    private XStream mapDefaultObjectModel(final XStream xstream) {
-        xstream.alias("bom", Bom.class);
-        xstream.aliasAttribute(Bom.class, "version", "version");
-        xstream.aliasAttribute(Bom.class, "serialNumber", "serialNumber");
-
-        xstream.alias("component", Component.class);
-        xstream.aliasAttribute(Component.class, "type", "type");
-        xstream.aliasAttribute(Component.class, "bomRef", "bom-ref");
-        xstream.aliasAttribute(Component.class, "scope", "scope");
-        xstream.registerConverter(new EnumToStringConverter<>(Component.Type.class, getComponentTypeMapping()));
-        xstream.registerConverter(new EnumToStringConverter<>(Component.Scope.class, getComponentScopeMapping()));
-
-        xstream.alias("hash", Hash.class);
-        xstream.registerConverter(new HashConverter());
-
-        xstream.alias("swid", Swid.class);
-        xstream.aliasAttribute(Swid.class, "tagId", "tagId");
-        xstream.aliasAttribute(Swid.class, "name", "name");
-        xstream.aliasAttribute(Swid.class, "version", "version");
-        xstream.aliasAttribute(Swid.class, "tagVersion", "tagVersion");
-        xstream.aliasAttribute(Swid.class, "patch", "patch");
-        xstream.registerConverter(new AttachmentTextConverter());
-        xstream.aliasField("text", Swid.class, "attachmentText");
-
-        xstream.alias("commit", Commit.class);
-
-        xstream.alias("reference", ExternalReference.class);
-        xstream.aliasAttribute(ExternalReference.class, "type", "type");
-        xstream.registerConverter(new EnumToStringConverter<>(ExternalReference.Type.class, getExternalReferenceTypeMapping()));
-
-        xstream.aliasField("licenses", Component.class, "licenseChoice");
-        xstream.alias("license", License.class);
-        xstream.addImplicitCollection(LicenseChoice.class, "licenses");
-        xstream.registerConverter(new AttachmentTextConverter());
-        xstream.aliasField("text", License.class, "attachmentText");
-
-        xstream.alias("pedigree", Pedigree.class);
-
-        xstream.addImplicitCollection(Dependency.class, "dependencies");
-        xstream.alias("dependency", Dependency.class);
-        xstream.aliasAttribute(Dependency.class, "ref", "ref");
-
-        xstream.alias("metadata", Metadata.class);
-        xstream.registerConverter(new ISO8601DateConverter());
-        xstream.alias("tool", Tool.class);
-        xstream.alias("author", OrganizationalContact.class);
-        xstream.alias("manufacture", OrganizationalEntity.class);
-        xstream.alias("supplier", OrganizationalEntity.class);
-        xstream.alias("contact", OrganizationalContact.class);
-        xstream.addImplicitCollection(OrganizationalEntity.class, "contacts");
-
-        return xstream;
-    }
 }
diff --git a/src/main/java/org/cyclonedx/util/CollectionTypeSerializer.java b/src/main/java/org/cyclonedx/util/CollectionTypeSerializer.java
new file mode 100644
index 000000000..d6039d573
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/CollectionTypeSerializer.java
@@ -0,0 +1,59 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.module.SimpleSerializers;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import org.cyclonedx.model.Dependency;
+
+public class CollectionTypeSerializer extends SimpleSerializers
+{
+  private final boolean useNamespace;
+
+  public CollectionTypeSerializer(final boolean useNamespace) {
+    this.useNamespace = useNamespace;
+  }
+
+  @Override
+  public JsonSerializer<?> findCollectionSerializer(SerializationConfig config,
+                                                    CollectionType type,
+                                                    BeanDescription beanDescription,
+                                                    TypeSerializer typeSerializer,
+                                                    JsonSerializer<Object> elementValueSerializer)
+  {
+    if (isDependencyListType(type)) {
+      return new DependencySerializer(useNamespace);
+    }
+    return findSerializer(config, type, beanDescription);
+  }
+
+  private boolean isDependencyListType(CollectionType type) {
+    CollectionType depArrayListType = TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, Dependency.class);
+    CollectionType depListType = TypeFactory.defaultInstance().constructCollectionType(List.class, Dependency.class);
+    return (type.equals(depArrayListType) || type.equals(depListType));
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/CustomDateSerializer.java b/src/main/java/org/cyclonedx/util/CustomDateSerializer.java
new file mode 100644
index 000000000..3cebec1ae
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/CustomDateSerializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+@SuppressWarnings("unused")
+public class CustomDateSerializer extends StdSerializer<Date>
+{
+  private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
+
+  public CustomDateSerializer() {
+    this(null);
+  }
+
+  public CustomDateSerializer(final Class t) {
+    super(t);
+  }
+
+  @Override
+  public void serialize(
+      final Date date, final JsonGenerator generator, final SerializerProvider provider)
+      throws IOException
+  {
+    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    generator.writeString(dateFormat.format(date));
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/DependencyDeserializer.java b/src/main/java/org/cyclonedx/util/DependencyDeserializer.java
new file mode 100644
index 000000000..d4a0c7803
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/DependencyDeserializer.java
@@ -0,0 +1,57 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
+import org.cyclonedx.model.Dependency;
+
+public class DependencyDeserializer extends StdDeserializer<List<Dependency>>
+{
+  public DependencyDeserializer() {
+    this(null);
+  }
+
+  public DependencyDeserializer(final Class<?> vc) {
+    super(vc);
+  }
+
+  @Override
+  public List<Dependency> deserialize(
+      final JsonParser parser, final DeserializationContext context)
+      throws IOException
+  {
+    Dependency[] dependencies = parser.readValueAs(Dependency[].class);
+    if (parser instanceof FromXmlParser) {
+      if (dependencies != null && dependencies.length > 0) {
+        return dependencies[0].getDependencies();
+      }
+    } else {
+      return Arrays.asList(dependencies.clone());
+    }
+
+    return null;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/DependencySerializer.java b/src/main/java/org/cyclonedx/util/DependencySerializer.java
new file mode 100644
index 000000000..ea99d153e
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/DependencySerializer.java
@@ -0,0 +1,110 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+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.Dependency;
+
+public class DependencySerializer extends StdSerializer<List<Dependency>>
+{
+  private final String NAMESPACE_PREFIX = "dg";
+  private final String NAMESPACE_URI = "http://cyclonedx.org/schema/ext/dependency-graph/1.0";
+  private boolean useNamespace = false;
+
+  public DependencySerializer(final boolean useNamespace) {
+    this(null);
+    this.useNamespace = useNamespace;
+  }
+
+  public DependencySerializer(final Class t) {
+    super(t);
+  }
+
+  @Override
+  public void serialize(
+      final List<Dependency> dependencies, final JsonGenerator generator, final SerializerProvider provider)
+      throws IOException
+  {
+    if (generator instanceof ToXmlGenerator) {
+      final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) generator;
+      final XMLStreamWriter staxWriter = toXmlGenerator.getStaxWriter();
+      try {
+        if (dependencies != null && !dependencies.isEmpty()) {
+          if (useNamespace) {
+            staxWriter.writeStartElement(NAMESPACE_PREFIX, "dependencies", NAMESPACE_URI);
+          } else {
+            staxWriter.writeStartElement("dependencies");
+          }
+          for (Dependency dependency : dependencies) {
+            writeDependency(dependency, staxWriter);
+          }
+        }
+      }
+      catch (XMLStreamException ex) {
+       throw new IOException(ex);
+      }
+    } else {
+      if (dependencies != null && !dependencies.isEmpty()) {
+        try {
+          generator.writeStartArray();
+          for (Dependency dependency : dependencies) {
+            generator.writeStartObject();
+            generator.writeStringField("ref", dependency.getRef());
+            generator.writeArrayFieldStart("dependsOn");
+            if (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty()) {
+              for (Dependency subDependency : dependency.getDependencies()) {
+                generator.writeString(subDependency.getRef());
+              }
+            }
+            generator.writeEndArray();
+            generator.writeEndObject();
+          }
+          generator.writeEndArray();
+        }
+        catch (IOException ex) {
+          throw new IOException(ex);
+        }
+      }
+    }
+  }
+
+  private void writeDependency(final Dependency dependency, final XMLStreamWriter writer) throws XMLStreamException {
+    if (useNamespace) {
+      writer.writeStartElement(NAMESPACE_PREFIX, "dependency", NAMESPACE_URI);
+    } else {
+      writer.writeStartElement( "dependency");
+    }
+    writer.writeAttribute("ref", dependency.getRef());
+    if (dependency.getDependencies() != null && !dependency.getDependencies().isEmpty()) {
+      for (Dependency subDependency : dependency.getDependencies()) {
+        writeDependency(subDependency, writer);
+      }
+    }
+    writer.writeEndElement();
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/ExtensibleTypesSerializer.java b/src/main/java/org/cyclonedx/util/ExtensibleTypesSerializer.java
new file mode 100644
index 000000000..f93bfc891
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/ExtensibleTypesSerializer.java
@@ -0,0 +1,90 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+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.Attribute;
+import org.cyclonedx.model.ExtensibleType;
+
+public class ExtensibleTypesSerializer extends StdSerializer<List<ExtensibleType>>
+{
+  private final String XMLNS = "xmlns";
+
+  private final String DEFAULT_VALID_NAMESPACE = "http://www.w3.org/1999/xhtml";
+
+  public ExtensibleTypesSerializer() {
+    this(null);
+  }
+
+  public ExtensibleTypesSerializer(final Class t) {
+    super(t);
+  }
+
+  @Override
+  public void serialize(
+      final List<ExtensibleType> extensibleTypes,
+      final JsonGenerator generator,
+      final SerializerProvider provider) throws IOException
+  {
+    final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) generator;
+    final XMLStreamWriter staxWriter = toXmlGenerator.getStaxWriter();
+    try {
+      if (extensibleTypes != null && !extensibleTypes.isEmpty()) {
+        for (ExtensibleType ext : extensibleTypes) {
+          if (ext.getAttributes() != null && !ext.getAttributes().isEmpty()) {
+            Attribute xmlNS = ext.getAttributes().stream()
+                .filter(a -> a.getKey().contains(XMLNS))
+                .findAny()
+                .orElse(null);
+
+            if (xmlNS != null) {
+              staxWriter.writeStartElement(ext.getNamespace(), ext.getName(), xmlNS.getValue());
+
+              for (Attribute attr : ext.getAttributes()) {
+                staxWriter.writeAttribute(attr.getKey(), attr.getValue());
+              }
+            }
+          } else {
+            staxWriter.writeStartElement(ext.getNamespace(), ext.getName(), DEFAULT_VALID_NAMESPACE);
+          }
+
+          if (ext.getExtensibleTypes() != null && !ext.getExtensibleTypes().isEmpty()) {
+            serialize(ext.getExtensibleTypes(), generator, provider);
+          }
+          if (ext.getValue() != null) {
+            staxWriter.writeCharacters(ext.getValue());
+          }
+          staxWriter.writeEndElement();
+        }
+      }
+    }
+    catch (XMLStreamException e) {
+      throw new IOException(e);
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java b/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
new file mode 100644
index 000000000..fb21c4161
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/ExtensionDeserializer.java
@@ -0,0 +1,252 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
+import org.cyclonedx.model.ExtensibleType;
+import org.cyclonedx.model.Extension;
+import org.cyclonedx.model.Extension.ExtensionType;
+import org.cyclonedx.model.vulnerability.Rating;
+import org.cyclonedx.model.vulnerability.Vulnerability10;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Advisory;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Cwe;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Recommendation;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Score;
+import org.cyclonedx.model.vulnerability.Vulnerability10.ScoreSource;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Severity;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Source;
+
+public class ExtensionDeserializer extends StdDeserializer<Extension>
+{
+  public ExtensionDeserializer() {
+    this(Extension.class);
+  }
+
+  public ExtensionDeserializer(final Class vc) {
+    super(vc);
+  }
+
+  @Override
+  public Extension deserialize(
+      final JsonParser p, final DeserializationContext ctxt) throws IOException
+  {
+    if (p.currentName().equals(Vulnerability10.VULNERABILITIES)) {
+      if (p instanceof FromXmlParser) {
+        return processVulnerabilities(p);
+      }
+    }
+    return null;
+  }
+
+  private Extension processVulnerabilities(final JsonParser parser) throws IOException {
+    TreeNode treeNode =  parser.readValueAsTree();
+    JsonNode vulnerabilityNode = (JsonNode) treeNode.get(Vulnerability10.NAME);
+    List<ExtensibleType> extensibleTypes = new ArrayList<>();
+    if (vulnerabilityNode.isArray() && !vulnerabilityNode.isEmpty()) {
+      for (JsonNode jn : vulnerabilityNode) {
+        Vulnerability10 vulnerability = processVulnerability(jn);
+        extensibleTypes.add(vulnerability);
+      }
+    } else {
+      Vulnerability10 vuln = processVulnerability(vulnerabilityNode);
+      extensibleTypes.add(vuln);
+    }
+    if (!extensibleTypes.isEmpty()) {
+      return createAndReturnExtension(ExtensionType.VULNERABILITIES, extensibleTypes);
+    }
+    return null;
+  }
+
+  private Vulnerability10 processVulnerability(final JsonNode vulnJson) {
+    Vulnerability10 vuln = new Vulnerability10();
+    for (Iterator<String> it = vulnJson.fieldNames(); it.hasNext(); ) {
+      String field = it.next();
+      switch (field) {
+        case Vulnerability10.REF:
+          vuln.setRef(vulnJson.get(field).textValue());
+          break;
+        case Vulnerability10.ID:
+          vuln.setId(vulnJson.get(field).textValue());
+          break;
+        case Vulnerability10.SOURCE:
+          vuln.setSource(processSource(vulnJson.get(field)));
+          break;
+        case Vulnerability10.RATINGS:
+          vuln.setRatings(processRatings(vulnJson.get(field)));
+          break;
+        case Vulnerability10.CWES:
+          vuln.setCwes(processCwes(vulnJson.get(field)));
+          break;
+        case Vulnerability10.DESCRIPTION:
+          vuln.setDescription(vulnJson.get(field).textValue());
+          break;
+        case Vulnerability10.RECOMMENDATIONS:
+          vuln.setRecommendations(processRecommendations(vulnJson.get(field)));
+          break;
+        case Vulnerability10.ADVISORIES:
+          vuln.setAdvisories(processAdvisories(vulnJson.get(field)));
+          break;
+        default:
+          // Unsupported field, skipped and not deserialized
+          break;
+      }
+    }
+    return vuln;
+  }
+
+  private List<Advisory> processAdvisories(final JsonNode advisories) {
+    List<Advisory> advisoryList = new ArrayList<>();
+    JsonNode adv = advisories.get(Vulnerability10.ADVISORY);
+    if (adv.isArray() && !adv.isEmpty()) {
+      for (JsonNode a : adv) {
+        advisoryList.add(processAdvisory(a));
+      }
+    } else {
+      advisoryList.add(processAdvisory(adv));
+    }
+    return advisoryList.isEmpty() ? null : advisoryList;
+  }
+
+  private Advisory processAdvisory(final JsonNode advisory) {
+    Advisory adv = new Advisory();
+    adv.setText(advisory.textValue());
+    return adv;
+  }
+
+  private List<Recommendation> processRecommendations(final JsonNode recommendations) {
+    List<Recommendation> recommendationsList = new ArrayList<>();
+    JsonNode rec = recommendations.get(Vulnerability10.RECOMMENDATION);
+    if (rec.isArray() && !rec.isEmpty()) {
+      for (JsonNode r : rec) {
+        recommendationsList.add(processRecommendation(r));
+      }
+    } else {
+      recommendationsList.add(processRecommendation(rec));
+    }
+    return recommendationsList.isEmpty() ? null : recommendationsList;
+  }
+
+  private Recommendation processRecommendation(final JsonNode recommendation) {
+    Recommendation rec = new Recommendation();
+    rec.setText(recommendation.textValue());
+    return rec;
+  }
+
+  private List<Cwe> processCwes(final JsonNode cwes) {
+    List<Cwe> cweList = new ArrayList<>();
+    JsonNode cwe = cwes.get(Vulnerability10.CWE);
+    if (cwe.isArray() && !cwe.isEmpty()) {
+      for (JsonNode c : cwe) {
+        cweList.add(processCwe(c));
+      }
+    } else {
+      processCwe(cwe);
+    }
+    return cweList.isEmpty() ? null : cweList;
+  }
+
+  private Cwe processCwe(final JsonNode cwe) {
+    Cwe c = new Cwe();
+    c.setText(Integer.valueOf(cwe.textValue()));
+    return c;
+  }
+
+  private Vulnerability10.Source processSource(final JsonNode sourceNode) {
+    Source source = new Source();
+    source.setName(getAsString("name", sourceNode));
+    if (sourceNode.get(Vulnerability10.URL) != null) {
+      try {
+        source.setUrl(new URL(sourceNode.get(Vulnerability10.URL).textValue()));
+      }
+      catch (MalformedURLException e) {
+        // Should we throw an exception? Is this worth stopping things over?
+        e.printStackTrace();
+      }
+    }
+    return source;
+  }
+
+  private List<Rating> processRatings(final JsonNode ratings) {
+    List<Rating> ratingsList = new ArrayList<>();
+    if (ratings.isArray() && !ratings.isEmpty()) {
+      for (JsonNode rating : ratings) {
+        ratingsList.add(processRating(rating));
+      }
+    } else {
+      ratingsList.add(processRating(ratings));
+    }
+    return ratingsList.isEmpty() ? null : ratingsList;
+  }
+
+  private Rating processRating(final JsonNode ratingNode) {
+    Rating rating = new Rating();
+    JsonNode r = ratingNode.get(Vulnerability10.RATING);
+    if (r.get(Vulnerability10.SCORE) != null) {
+      Score score = new Score();
+      JsonNode s = r.get(Vulnerability10.SCORE);
+      score.setBase(getAsDouble(Vulnerability10.BASE, s));
+      score.setImpact(getAsDouble(Vulnerability10.IMPACT, s));
+      score.setExploitability(getAsDouble(Vulnerability10.EXPLOITABILITY, s));
+      rating.setScore(score);
+    }
+    rating.setSeverity(Severity.fromString(getAsString(Vulnerability10.SEVERITY, r)));
+    rating.setMethod(ScoreSource.fromString(getAsString(Vulnerability10.METHOD, r)));
+    rating.setVector(getAsString(Vulnerability10.VECTOR, r));
+
+    return rating;
+  }
+
+  private Extension createAndReturnExtension(final ExtensionType extType, final List<ExtensibleType> list) {
+    if (extType == ExtensionType.VULNERABILITIES) {
+      Extension ext = new Extension(extType, list);
+
+      ext.setNamespaceURI(Vulnerability10.NAMESPACE_URI);
+      ext.setPrefix(Vulnerability10.PREFIX);
+
+      return ext;
+    }
+    return null;
+  }
+
+  private Double getAsDouble(final String fieldName, final JsonNode node) {
+    if (node.get(fieldName) != null) {
+      return node.get(fieldName).asDouble();
+    }
+    return null;
+  }
+
+  private String getAsString(final String fieldName, final JsonNode node) {
+    if (node.get(fieldName) != null) {
+      return node.get(fieldName).textValue();
+    }
+    return null;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/ExtensionSerializer.java b/src/main/java/org/cyclonedx/util/ExtensionSerializer.java
new file mode 100644
index 000000000..0a432e79f
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/ExtensionSerializer.java
@@ -0,0 +1,178 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+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.ExtensibleType;
+import org.cyclonedx.model.Extension;
+import org.cyclonedx.model.Extension.ExtensionType;
+import org.cyclonedx.model.vulnerability.Rating;
+import org.cyclonedx.model.vulnerability.Vulnerability10;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Advisory;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Cwe;
+import org.cyclonedx.model.vulnerability.Vulnerability10.Recommendation;
+
+public class ExtensionSerializer
+    extends StdSerializer<Extension>
+{
+  public ExtensionSerializer() {
+    this(null);
+  }
+
+  public ExtensionSerializer(final Class t) {
+    super(t);
+  }
+
+  @Override
+  public void serialize(
+      final Extension value, final JsonGenerator gen, final SerializerProvider provider) throws IOException
+  {
+    if (value.getExtensionType().equals(ExtensionType.VULNERABILITIES)) {
+      if (gen instanceof ToXmlGenerator) {
+        try {
+          serializeVulnerabilities((ToXmlGenerator) gen, value);
+        }
+        catch (XMLStreamException e) {
+          throw new IOException(e);
+        }
+      }
+    }
+  }
+
+  private void serializeVulnerabilities(final ToXmlGenerator gen, final Extension vulns)
+      throws XMLStreamException
+  {
+    final XMLStreamWriter staxWriter = gen.getStaxWriter();
+    staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.VULNERABILITIES, Vulnerability10.NAMESPACE_URI);
+
+    for (ExtensibleType ext : vulns.getExtensions()) {
+      final Vulnerability10 vuln = (Vulnerability10) ext;
+      staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.NAME, Vulnerability10.NAMESPACE_URI);
+      staxWriter.writeAttribute(Vulnerability10.REF, vuln.getRef());
+
+      generateTextNode(staxWriter, Vulnerability10.ID, vuln.getId(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+      processRatings(staxWriter, vuln);
+      processCwes(staxWriter, vuln);
+      generateTextNode(staxWriter, Vulnerability10.DESCRIPTION, vuln.getDescription(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+      processRecommendations(staxWriter, vuln);
+      processAdvisories(staxWriter, vuln);
+
+      staxWriter.writeEndElement();
+    }
+
+    staxWriter.writeEndElement();
+  }
+
+  private void processAdvisories(final XMLStreamWriter staxWriter, final Vulnerability10 vuln)
+      throws XMLStreamException
+  {
+    if (vuln.getAdvisories() != null && !vuln.getAdvisories().isEmpty()) {
+      staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.ADVISORIES, Vulnerability10.NAMESPACE_URI);
+      for (Advisory a : vuln.getAdvisories()) {
+        generateTextNode(staxWriter, Vulnerability10.ADVISORY, a.getText(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+      }
+      staxWriter.writeEndElement();
+    }
+  }
+
+  private void processRecommendations(final XMLStreamWriter staxWriter, final Vulnerability10 vuln)
+      throws XMLStreamException
+  {
+    if (vuln.getRecommendations() != null && !vuln.getRecommendations().isEmpty()) {
+      staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.RECOMMENDATIONS, Vulnerability10.NAMESPACE_URI);
+      for (Recommendation r : vuln.getRecommendations()) {
+        generateTextNode(staxWriter, Vulnerability10.RECOMMENDATION, r.getText(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+      }
+      staxWriter.writeEndElement();
+    }
+  }
+
+  private void processCwes(final XMLStreamWriter staxWriter, final Vulnerability10 vuln)
+      throws XMLStreamException
+  {
+    if (vuln.getCwes() != null && !vuln.getCwes().isEmpty()) {
+      staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.CWES, Vulnerability10.NAMESPACE_URI);
+      for (Cwe c : vuln.getCwes()) {
+        generateTextNodeFromNumber(staxWriter, Vulnerability10.CWE, c.getText(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+      }
+      staxWriter.writeEndElement();
+    }
+  }
+
+  private void processRatings(final XMLStreamWriter staxWriter, final Vulnerability10 vuln)
+      throws XMLStreamException
+  {
+    if (vuln.getRatings() != null && !vuln.getRatings().isEmpty()) {
+      staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.RATINGS, Vulnerability10.NAMESPACE_URI);
+      for (Rating r : vuln.getRatings()) {
+        staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.RATING, Vulnerability10.NAMESPACE_URI);
+        if (r.getScore() != null) {
+          staxWriter.writeStartElement(Vulnerability10.PREFIX, Vulnerability10.SCORE, Vulnerability10.NAMESPACE_URI);
+          generateTextNodeFromNumber(staxWriter, Vulnerability10.BASE, r.getScore().getBase(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+          generateTextNodeFromNumber(staxWriter, Vulnerability10.IMPACT, r.getScore().getImpact(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+          generateTextNodeFromNumber(staxWriter, Vulnerability10.EXPLOITABILITY, r.getScore().getExploitability(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+          staxWriter.writeEndElement();
+        }
+        if (r.getSeverity() != null) {
+          generateTextNode(staxWriter, Vulnerability10.SEVERITY, r.getSeverity().getSeverityName(),
+              Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+        }
+        if (r.getMethod() != null) {
+          generateTextNode(staxWriter, Vulnerability10.METHOD, r.getMethod().getScoreSourceName(),
+              Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+        }
+        generateTextNode(staxWriter, Vulnerability10.VECTOR, r.getVector(), Vulnerability10.NAMESPACE_URI, Vulnerability10.PREFIX);
+        staxWriter.writeEndElement();
+      }
+      staxWriter.writeEndElement();
+    }
+  }
+
+  private void generateTextNodeFromNumber(final XMLStreamWriter writer,
+                                          final String fieldName,
+                                          final Number value,
+                                          final String namespaceUri,
+                                          final String prefix) throws XMLStreamException
+  {
+    if ( value != null) {
+      generateTextNode(writer, fieldName, value.toString(), namespaceUri, prefix);
+    }
+  }
+
+  private void generateTextNode(final XMLStreamWriter writer,
+                                final String fieldName,
+                                final String value,
+                                final String namespaceUri,
+                                final String prefix) throws XMLStreamException
+  {
+    if ( value != null) {
+      writer.writeStartElement(prefix, fieldName, namespaceUri);
+      writer.writeCharacters(value);
+      writer.writeEndElement();
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/ExternalReferenceSerializer.java b/src/main/java/org/cyclonedx/util/ExternalReferenceSerializer.java
new file mode 100644
index 000000000..62157d938
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/ExternalReferenceSerializer.java
@@ -0,0 +1,81 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+import java.util.function.BiPredicate;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+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.ExternalReference;
+import org.cyclonedx.model.ExternalReference.Type;
+
+public class ExternalReferenceSerializer extends StdSerializer<ExternalReference>
+{
+  public ExternalReferenceSerializer() {
+    this(null);
+  }
+
+  public ExternalReferenceSerializer(final Class t) {
+    super(t);
+  }
+
+  @Override
+  public void serialize(
+      final ExternalReference extRef, final JsonGenerator gen, final SerializerProvider provider) throws IOException
+  {
+    final BiPredicate<Type, String> validateExternalReference = (type, url) -> (type != null && url != null && BomUtils.validateUrlString(url));
+    if (gen instanceof ToXmlGenerator) {
+      final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
+      final XMLStreamWriter staxWriter = toXmlGenerator.getStaxWriter();
+
+      if (validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
+        try {
+          staxWriter.writeStartElement("reference");
+          staxWriter.writeAttribute("type", extRef.getType().getTypeName());
+          staxWriter.writeStartElement("url");
+          staxWriter.writeCharacters(extRef.getUrl());
+          staxWriter.writeEndElement();
+          if (extRef.getComment() != null) {
+            staxWriter.writeStartElement("comment");
+            staxWriter.writeCharacters(extRef.getComment());
+            staxWriter.writeEndElement();
+          }
+          staxWriter.writeEndElement();
+        }
+        catch (XMLStreamException ex) {
+          throw new IOException(ex);
+        }
+      }
+    } else if (validateExternalReference.test(extRef.getType(), extRef.getUrl())) {
+      gen.writeStartObject();
+      gen.writeStringField("type", extRef.getType().getTypeName());
+      gen.writeStringField("url", extRef.getUrl());
+      if (extRef.getComment() != null) {
+        gen.writeStringField("comment", extRef.getComment());
+      }
+      gen.writeEndObject();
+    }
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/LicenseChoiceSerializer.java b/src/main/java/org/cyclonedx/util/LicenseChoiceSerializer.java
new file mode 100644
index 000000000..11a800674
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/LicenseChoiceSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+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 org.cyclonedx.model.License;
+import org.cyclonedx.model.LicenseChoice;
+
+public class LicenseChoiceSerializer extends StdSerializer<LicenseChoice>
+{
+  public LicenseChoiceSerializer() {
+    this(LicenseChoice.class);
+  }
+
+  public LicenseChoiceSerializer(final Class t) {
+    super(t);
+  }
+
+  @Override
+  public void serialize(
+      final LicenseChoice lc, final JsonGenerator gen, final SerializerProvider provider)
+      throws IOException
+  {
+    gen.writeStartArray();
+    if (lc != null && lc.getLicenses() != null && !lc.getLicenses().isEmpty()) {
+      for (License l : lc.getLicenses()) {
+        gen.writeStartObject();
+        provider.defaultSerializeField("license", l, gen);
+        gen.writeEndObject();
+      }
+    } else if (lc != null && lc.getExpression() != null) {
+      gen.writeStartObject();
+      gen.writeStringField("expression", lc.getExpression());
+      gen.writeEndObject();
+    }
+    gen.writeEndArray();
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/LicenseDeserializer.java b/src/main/java/org/cyclonedx/util/LicenseDeserializer.java
new file mode 100644
index 000000000..79b97856e
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/LicenseDeserializer.java
@@ -0,0 +1,67 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+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.type.MapType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
+import org.cyclonedx.model.License;
+import org.cyclonedx.model.LicenseChoice;
+
+public class LicenseDeserializer extends JsonDeserializer<LicenseChoice>
+{
+  private final ObjectMapper mapper;
+
+  public LicenseDeserializer() {
+    this.mapper = new ObjectMapper();
+  }
+
+  @Override
+  public LicenseChoice deserialize(
+      final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException
+  {
+    if (p instanceof FromXmlParser) {
+      return p.readValueAs(LicenseChoice.class);
+    }
+    ArrayNode nodes = p.readValueAsTree();
+
+    TypeFactory factory = TypeFactory.defaultInstance();
+    MapType type = factory.constructMapType(HashMap.class, String.class, License.class);
+    LicenseChoice licenseChoice = new LicenseChoice();
+    for (JsonNode node : nodes) {
+      HashMap<String, License> map = this.mapper.readValue(node.toString(), type);
+
+      if (map.get("license") != null) {
+        licenseChoice.addLicense(map.get("license"));
+      }
+    }
+
+    return licenseChoice;
+  }
+}
diff --git a/src/main/java/org/cyclonedx/util/VersionAnnotationIntrospector.java b/src/main/java/org/cyclonedx/util/VersionAnnotationIntrospector.java
new file mode 100644
index 000000000..bb30fd801
--- /dev/null
+++ b/src/main/java/org/cyclonedx/util/VersionAnnotationIntrospector.java
@@ -0,0 +1,49 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.cyclonedx.util;
+
+import java.util.Arrays;
+
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+import com.fasterxml.jackson.dataformat.xml.JacksonXmlAnnotationIntrospector;
+import org.cyclonedx.model.JsonOnly;
+import org.cyclonedx.model.VersionFilter;
+
+public class VersionAnnotationIntrospector extends JacksonXmlAnnotationIntrospector
+{
+  private final String version;
+
+  public VersionAnnotationIntrospector(final String version) {
+    this.version = version;
+  }
+
+  @Override
+  public boolean hasIgnoreMarker(final AnnotatedMember m) {
+    if (m.hasAnnotation(VersionFilter.class)) {
+      VersionFilter filter = m.getAnnotation(VersionFilter.class);
+      if (Arrays.stream(filter.versions()).noneMatch(v -> v.equals(version))) {
+        return true;
+      }
+    }
+    if (m.hasAnnotation(JsonOnly.class)) {
+      return true;
+    }
+    return super.hasIgnoreMarker(m);
+  }
+}


Test patch:
diff --git a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
index a4f006a44..a6e662d7d 100644
--- a/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomXmlGeneratorTest.java
@@ -19,6 +19,7 @@
 package org.cyclonedx;
 
 import org.apache.commons.io.IOUtils;
+import org.cyclonedx.CycloneDxSchema.Version;
 import org.cyclonedx.generators.xml.BomXmlGenerator;
 import org.cyclonedx.generators.xml.BomXmlGenerator10;
 import org.cyclonedx.generators.xml.BomXmlGenerator11;
@@ -34,12 +35,24 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.w3c.dom.Document;
+
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 
+import javax.xml.XMLConstants;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
 public class BomXmlGeneratorTest {
 
     private File tempFile;
@@ -58,19 +71,23 @@ public class BomXmlGeneratorTest {
 
     @Test
     public void schema10GenerationTest() throws Exception {
-        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_10, createCommonBom());
-        generator.generate();
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_10, createCommonBom("/bom-1.0.xml"));
+        Document doc = generator.generate();
+        testDocument(doc);
+
         Assert.assertTrue(generator instanceof BomXmlGenerator10);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_10, generator.getSchemaVersion());
         File file = writeToFile(generator.toXmlString());
         XmlParser parser = new XmlParser();
-        Assert.assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_10));
+        Assert.assertTrue(parser.isValid(file, Version.VERSION_10));
     }
 
     @Test
-    public void schema11GenerationTest() throws Exception {
-        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_11, createCommonBom());
-        generator.generate();
+    public void schema11WithDependencyGraphGenerationTest() throws Exception {
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_11, createCommonBom("/bom-1.1-dependency-graph-1.0.xml"));
+        Document doc = generator.generate();
+        testDocument(doc);
+
         Assert.assertTrue(generator instanceof BomXmlGenerator11);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_11, generator.getSchemaVersion());
         File file = writeToFile(generator.toXmlString());
@@ -79,9 +96,24 @@ public class BomXmlGeneratorTest {
     }
 
     @Test
-    public void schema11WithDependencyGraphGenerationTest() throws Exception {
-        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_11, createCommonBom("/bom-1.1-dependency-graph-1.0.xml"));
-        generator.generate();
+    public void schema11WithVulnerabilitiesGenerationTest() throws Exception {
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_11, createCommonBom("/bom-1.1-vulnerability-1.0.xml"));
+        Document doc = generator.generate();
+        testDocument(doc);
+
+        Assert.assertTrue(generator instanceof BomXmlGenerator11);
+        Assert.assertEquals(CycloneDxSchema.Version.VERSION_11, generator.getSchemaVersion());
+        File file = writeToFile(generator.toXmlString());
+        XmlParser parser = new XmlParser();
+        Assert.assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_11));
+    }
+
+    @Test
+    public void schema11GenerationTest() throws Exception {
+        BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_11, createCommonBom());
+        Document doc = generator.generate();
+        testDocument(doc);
+
         Assert.assertTrue(generator instanceof BomXmlGenerator11);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_11, generator.getSchemaVersion());
         File file = writeToFile(generator.toXmlString());
@@ -92,7 +124,9 @@ public class BomXmlGeneratorTest {
     @Test
     public void schema12GenerationTestWith11Data() throws Exception {
         BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_12, createCommonBom());
-        generator.generate();
+        Document doc = generator.generate();
+        testDocument(doc);
+
         Assert.assertTrue(generator instanceof BomXmlGenerator12);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_12, generator.getSchemaVersion());
         File file = writeToFile(generator.toXmlString());
@@ -103,7 +137,9 @@ public class BomXmlGeneratorTest {
     @Test
     public void schema12GenerationTest() throws Exception {
         BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_12, createCommonBom("/bom-1.2.xml"));
-        generator.generate();
+        Document doc = generator.generate();
+        testDocument(doc);
+
         Assert.assertTrue(generator instanceof BomXmlGenerator12);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_12, generator.getSchemaVersion());
         File file = writeToFile(generator.toXmlString());
@@ -133,7 +169,9 @@ public class BomXmlGeneratorTest {
         Bom bom = new Bom();
         bom.addComponent(c);
         BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_11, bom);
-        generator.generate();
+        Document doc = generator.generate();
+
+        testDocument(doc);
         Assert.assertTrue(generator instanceof BomXmlGenerator11);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_11, generator.getSchemaVersion());
         File file = writeToFile(generator.toXmlString());
@@ -147,6 +185,7 @@ public class BomXmlGeneratorTest {
         c.setName("Component-A");
         c.setType(Component.Type.LIBRARY);
         c.setVersion("1.0.0");
+
         c.addExtensibleType(new ExtensibleType("foo", "comp-bar", "my-comp-value"));
 
         License l = new License();
@@ -167,7 +206,9 @@ public class BomXmlGeneratorTest {
         bom.addExtensibleType(t1);
 
         BomXmlGenerator generator = BomGeneratorFactory.createXml(CycloneDxSchema.Version.VERSION_11, bom);
-        generator.generate();
+        Document doc = generator.generate();
+        testDocument(doc);
+
         Assert.assertTrue(generator instanceof BomXmlGenerator11);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_11, generator.getSchemaVersion());
         File file = writeToFile(generator.toXmlString());
@@ -191,4 +232,29 @@ public class BomXmlGeneratorTest {
         XmlParser parser = new XmlParser();
         return parser.parse(bomBytes);
     }
+
+    private void testDocument(Document doc) {
+        Assert.assertNotNull(doc);
+        Assert.assertNotNull(documentToString(doc));
+    }
+
+    private String documentToString(Document doc) {
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer transformer;
+        try {
+            tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+            transformer = tf.newTransformer();
+
+            transformer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+            StringWriter sw = new StringWriter();
+            transformer.transform(new DOMSource(doc), new StreamResult(sw));
+            return sw.getBuffer().toString();
+        } catch (TransformerException ex) {
+            return null;
+        }
+    }
 }
diff --git a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
index edbc5da21..525cf5577 100644
--- a/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/JsonParserTest.java
@@ -70,12 +70,12 @@ public class JsonParserTest {
         Assert.assertEquals(0, bom.getMetadata().getComponent().getSwid().getTagVersion());
         Assert.assertEquals(false, bom.getMetadata().getComponent().getSwid().isPatch());
         Assert.assertEquals("Acme, Inc.", bom.getMetadata().getManufacture().getName());
-        Assert.assertEquals("[\"https://example.com\"]", bom.getMetadata().getManufacture().getUrl()); // TODO: fix this
+        Assert.assertEquals("https://example.com", bom.getMetadata().getManufacture().getUrls().get(0));
         Assert.assertEquals(1, bom.getMetadata().getManufacture().getContacts().size());
         Assert.assertEquals("Acme Professional Services", bom.getMetadata().getManufacture().getContacts().get(0).getName());
         Assert.assertEquals("professional.services@example.com", bom.getMetadata().getManufacture().getContacts().get(0).getEmail());
         Assert.assertEquals("Acme, Inc.", bom.getMetadata().getSupplier().getName());
-        Assert.assertEquals("[\"https://example.com\"]", bom.getMetadata().getSupplier().getUrl()); // TODO: fix this
+        Assert.assertEquals("https://example.com", bom.getMetadata().getSupplier().getUrls().get(0));
         Assert.assertEquals(1, bom.getMetadata().getSupplier().getContacts().size());
         Assert.assertEquals("Acme Distribution", bom.getMetadata().getSupplier().getContacts().get(0).getName());
         Assert.assertEquals("distribution@example.com", bom.getMetadata().getSupplier().getContacts().get(0).getEmail());
diff --git a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
index 595815ddc..6e797cdc1 100644
--- a/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
+++ b/src/test/java/org/cyclonedx/parsers/XmlParserTest.java
@@ -76,8 +76,8 @@ public class XmlParserTest {
         final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/bom-1.0.xml"));
         final XmlParser parser = new XmlParser();
         final Bom bom = parser.parse(bomBytes);
-        Assert.assertEquals("1.0", bom.getSpecVersion());
         Assert.assertEquals(1, bom.getComponents().size());
+        Assert.assertEquals("1.0", bom.getSpecVersion());
         Assert.assertEquals(1, bom.getVersion());
         final List<Component> components = bom.getComponents();
         Assert.assertEquals(1, components.size());
@@ -102,8 +102,8 @@ public class XmlParserTest {
         final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/bom-1.1.xml"));
         final XmlParser parser = new XmlParser();
         final Bom bom = parser.parse(bomBytes);
-        Assert.assertEquals("1.1", bom.getSpecVersion());
         Assert.assertEquals(3, bom.getComponents().size());
+        Assert.assertEquals("1.1", bom.getSpecVersion());
         Assert.assertEquals(1, bom.getVersion());
         final List<Component> components = bom.getComponents();
         Assert.assertEquals(3, components.size());
@@ -148,8 +148,8 @@ public class XmlParserTest {
         final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/bom-1.1-dependency-graph-1.0.xml"));
         final XmlParser parser = new XmlParser();
         final Bom bom = parser.parse(bomBytes);
-        Assert.assertEquals("1.1", bom.getSpecVersion());
         Assert.assertEquals(3, bom.getComponents().size());
+        Assert.assertEquals("1.1", bom.getSpecVersion());
         Assert.assertEquals(1, bom.getVersion());
         final List<Component> components = bom.getComponents();
         Assert.assertEquals(3, components.size());
@@ -179,8 +179,8 @@ public class XmlParserTest {
         final byte[] bomBytes = IOUtils.toByteArray(this.getClass().getResourceAsStream("/bom-1.2.xml"));
         final XmlParser parser = new XmlParser();
         final Bom bom = parser.parse(bomBytes);
-        Assert.assertEquals("1.2", bom.getSpecVersion());
         Assert.assertEquals(3, bom.getComponents().size());
+        Assert.assertEquals("1.2", bom.getSpecVersion());
         Assert.assertEquals(1, bom.getVersion());
         Assert.assertNotNull(bom.getMetadata());
         Assert.assertNotNull(bom.getMetadata().getTimestamp());
@@ -205,12 +205,12 @@ public class XmlParserTest {
         Assert.assertEquals(0, bom.getMetadata().getComponent().getSwid().getTagVersion());
         Assert.assertEquals(false, bom.getMetadata().getComponent().getSwid().isPatch());
         Assert.assertEquals("Acme, Inc.", bom.getMetadata().getManufacture().getName());
-        Assert.assertEquals("https://example.com", bom.getMetadata().getManufacture().getUrl());
+        Assert.assertEquals("https://example.com", bom.getMetadata().getManufacture().getUrls().get(0));
         Assert.assertEquals(1, bom.getMetadata().getManufacture().getContacts().size());
         Assert.assertEquals("Acme Professional Services", bom.getMetadata().getManufacture().getContacts().get(0).getName());
         Assert.assertEquals("professional.services@example.com", bom.getMetadata().getManufacture().getContacts().get(0).getEmail());
         Assert.assertEquals("Acme, Inc.", bom.getMetadata().getSupplier().getName());
-        Assert.assertEquals("https://example.com", bom.getMetadata().getSupplier().getUrl());
+        Assert.assertEquals("https://example.com", bom.getMetadata().getSupplier().getUrls().get(0));
         Assert.assertEquals(1, bom.getMetadata().getSupplier().getContacts().size());
         Assert.assertEquals("Acme Distribution", bom.getMetadata().getSupplier().getContacts().get(0).getName());
         Assert.assertEquals("distribution@example.com", bom.getMetadata().getSupplier().getContacts().get(0).getEmail());
diff --git a/src/test/resources/bom-1.2.xml b/src/test/resources/bom-1.2.xml
index dfd7137c9..28d86b26a 100644
--- a/src/test/resources/bom-1.2.xml
+++ b/src/test/resources/bom-1.2.xml
@@ -30,6 +30,7 @@
         <manufacture>
             <name>Acme, Inc.</name>
             <url>https://example.com</url>
+            <url>https://example2.com</url>
             <contact>
                 <name>Acme Professional Services</name>
                 <email>professional.services@example.com</email>