Repository: CycloneDX/cyclonedx-core-java
Pull request #83 (2021-03-21 22:08:27)

Drop dependency on org.json, everitt, use networknt / json-schema-validator for validation, javax for JsonObject
This PR was constructed for a few reasons:

- `org.json` license specifies it cannot be used for evil. While we are not evil (or we'd like to think so), evil is quite subjective, look at Dr. Evil for example, is he really all evil?! Thus it becomes a philosophers debate. Lawyers seem to not like philosophy? Odd!
- Adding `javax.json.JsonObject` as a comparable alternative after talking with @stevespringett , this is dual licensed CDDL 1.1 and GPL 2.0 with the ClassPath Exception (so no open sourcing your work if you are just using the library)
- Removing `everit/json-schema` as the library is more or less tied to `org.json`
- Moves from `everit/json-schema` to: `networknt/json-schema-validator`, designed for use with Jackson and faster than Everit (subjective, I'm sure), this however solves a licensing issue that is buried in `everit`, which is it uses Google's `re2j`, which is licensed pretty fine, actually, but the way it is licensed is confusing to many who end up using it because it will show up as The Google License, when it's really just a slightly modified BSD3: https://github.com/google/re2j/issues/122

Due to the changes, some API's have been slightly tweaked, but kept as consistent as possible to ensure very little rework to upstream consumers (hopefully!)


Patch:
diff --git a/.gitignore b/.gitignore
index db47dfc46..1bbbc2b1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ target
 *.iml
 .idea/
 dependency-reduced-pom.xml
+.DS_Store
diff --git a/pom.xml b/pom.xml
index f96a2c658..c286a6d6e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -140,12 +140,15 @@
             <version>2.8.0</version>
         </dependency>
         <!-- Package URL -->
+
         <dependency>
             <groupId>com.github.package-url</groupId>
             <artifactId>packageurl-java</artifactId>
             <version>1.2.0</version>
         </dependency>
+
         <!-- XML Parser, Generator, JSON Parser -->
+
         <dependency>
             <groupId>com.fasterxml.jackson.dataformat</groupId>
             <artifactId>jackson-dataformat-xml</artifactId>
@@ -157,26 +160,38 @@
             <artifactId>xercesImpl</artifactId>
             <version>2.12.1</version>
         </dependency>
-        <!-- JSON Generator library -->
+
+        <!-- JSON Schema library -->
+
         <dependency>
-            <groupId>org.json</groupId>
-            <artifactId>json</artifactId>
-            <version>20210307</version>
+            <groupId>com.networknt</groupId>
+            <artifactId>json-schema-validator</artifactId>
+            <version>1.0.50</version>
         </dependency>
-        <!-- JSON Schema library -->
+
+        <!-- JsonObject supporting libraries -->
+
         <dependency>
-            <groupId>com.github.erosb</groupId>
-            <artifactId>everit-json-schema</artifactId>
-            <version>1.12.2</version>
+            <groupId>javax.json</groupId>
+            <artifactId>javax.json-api</artifactId>
+            <version>1.1.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish</groupId>
+            <artifactId>javax.json</artifactId>
+            <version>1.1.4</version>
         </dependency>
 
         <!-- Unit Test -->
+
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.13.2</version>
             <scope>test</scope>
         </dependency>
+
     </dependencies>
 
     <build>
diff --git a/src/main/java/org/cyclonedx/CycloneDxSchema.java b/src/main/java/org/cyclonedx/CycloneDxSchema.java
index 32874b8c7..542fa6625 100644
--- a/src/main/java/org/cyclonedx/CycloneDxSchema.java
+++ b/src/main/java/org/cyclonedx/CycloneDxSchema.java
@@ -18,11 +18,13 @@
  */
 package org.cyclonedx;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersionDetector;
 import org.cyclonedx.generators.json.BomJsonGenerator;
 import org.cyclonedx.generators.xml.BomXmlGenerator;
-import org.everit.json.schema.loader.SchemaLoader;
-import org.json.JSONObject;
-import org.json.JSONTokener;
 import org.xml.sax.SAXException;
 import javax.xml.XMLConstants;
 import javax.xml.transform.Source;
@@ -31,8 +33,6 @@ import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
 
 /**
  * CycloneDxSchema is a base class that provides schema information to
@@ -73,16 +73,30 @@ public abstract class CycloneDxSchema {
     }
 
     /**
-     * Returns the CycloneDX JSON Schema for the specified schema version.
+     * Returns the CycloneDX JsonSchema for the specified schema version.
      * @param schemaVersion The version to return the schema for
      * @param strict if true, the strict schema will be used which prohibits non-defined properties from being present in the BOM
      * @return a Schema
      * @throws IOException when errors are encountered
-     * @throws URISyntaxException when errors are encountered
-     * @since 3.0.0
+     * @since 5.0.0
      */
-    public org.everit.json.schema.Schema getJsonSchema(CycloneDxSchema.Version schemaVersion, boolean strict) throws IOException, URISyntaxException {
-        return getJsonSchema12(strict);
+    public JsonSchema getJsonSchema(CycloneDxSchema.Version schemaVersion, boolean strict, final ObjectMapper mapper)
+        throws IOException
+    {
+        final InputStream spdxInstream = getJsonSchemaAsStream(schemaVersion, strict);
+
+        JsonNode schemaNode = mapper.readTree(spdxInstream);
+
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(schemaNode));
+
+        return factory.getSchema(schemaNode);
+    }
+
+    private InputStream getJsonSchemaAsStream(final CycloneDxSchema.Version schemaVersion, boolean strict) throws IOException {
+        if (strict) {
+            return this.getClass().getClassLoader().getResourceAsStream("bom-1.2-strict.schema.json");
+        }
+        return this.getClass().getClassLoader().getResourceAsStream("bom-1.2.schema.json");
     }
 
     /**
@@ -152,23 +166,4 @@ public abstract class CycloneDxSchema {
         }
         return schemaFactory.newSchema(schemaFiles);
     }
-
-    private org.everit.json.schema.Schema getJsonSchema12(boolean strict) throws IOException, URISyntaxException {
-        if (strict) {
-            return getJsonSchema(this.getClass().getClassLoader().getResourceAsStream("bom-1.2-strict.schema.json"));
-        } else {
-            return getJsonSchema(this.getClass().getClassLoader().getResourceAsStream("bom-1.2.schema.json"));
-        }
-    }
-
-    private org.everit.json.schema.Schema getJsonSchema(InputStream inputStream) throws IOException, URISyntaxException {
-        final JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream));
-        final InputStream spdxInstream = this.getClass().getClassLoader().getResourceAsStream("spdx.schema.json");
-        final JSONObject spdxSchema = new JSONObject(new JSONTokener(spdxInstream));
-        final SchemaLoader schemaLoader = SchemaLoader.builder()
-                .registerSchemaByURI(new URI("http://cyclonedx.org/schema/spdx.schema.json"), spdxSchema)
-                .schemaJson(rawSchema)
-                .build();
-        return schemaLoader.load().build();
-    }
 }
diff --git a/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java b/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
index eebba023b..a6df25924 100644
--- a/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/json/AbstractBomJsonGenerator.java
@@ -28,7 +28,6 @@ import org.cyclonedx.util.CollectionTypeSerializer;
 import org.cyclonedx.util.ComponentWrapperSerializer;
 import org.cyclonedx.util.LicenseChoiceSerializer;
 import org.cyclonedx.util.TrimStringSerializer;
-import org.json.JSONObject;
 
 abstract class AbstractBomJsonGenerator extends CycloneDxSchema implements BomJsonGenerator {
 
@@ -61,8 +60,6 @@ abstract class AbstractBomJsonGenerator extends CycloneDxSchema implements BomJs
         mapper.registerModule(componentWrapperModule);
     }
 
-    JSONObject doc = OrderedJSONObjectFactory.create();
-
     String toJson(final Bom bom, final boolean prettyPrint) throws GeneratorException {
         try {
             if (prettyPrint) {
diff --git a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java
index faa0db77e..4159f7224 100644
--- a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java
+++ b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator.java
@@ -18,14 +18,15 @@
  */
 package org.cyclonedx.generators.json;
 
+import javax.json.JsonObject;
+
 import org.cyclonedx.CycloneDxSchema;
-import org.json.JSONObject;
 
 public interface BomJsonGenerator {
 
     CycloneDxSchema.Version getSchemaVersion();
 
-    JSONObject generate();
+    JsonObject toJsonObject();
 
     String toJsonString();
 
diff --git a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java
index 32fb7b4f3..3372f5706 100644
--- a/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java
+++ b/src/main/java/org/cyclonedx/generators/json/BomJsonGenerator12.java
@@ -18,18 +18,21 @@
  */
 package org.cyclonedx.generators.json;
 
+import java.io.StringReader;
 import java.lang.reflect.Field;
 
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
 import org.cyclonedx.CycloneDxSchema;
 import org.cyclonedx.exception.GeneratorException;
 import org.cyclonedx.model.Bom;
 import org.cyclonedx.model.Component;
-import org.json.JSONObject;
 
 /**
  * BomGenerator creates a CycloneDX bill-of-material document from a set of
- * {@link Component}s. Proper usage assumes {@link #generate()} is called after
- * construction and optionally followed by {@link #toJsonString()}.
+ * {@link Component}s.
  * @since 3.0.0
  */
 public class BomJsonGenerator12 extends AbstractBomJsonGenerator implements BomJsonGenerator {
@@ -61,15 +64,14 @@ public class BomJsonGenerator12 extends AbstractBomJsonGenerator implements BomJ
     /**
      * Creates a CycloneDX BOM from a set of Components.
      * @return an XML Document representing a CycloneDX BoM
-     * @since 3.0.0
+     * @since 5.0.0
      */
-    public JSONObject generate() {
+    public JsonObject toJsonObject() {
         try {
-            doc = new JSONObject(toJson(this.bom, false));
+            JsonReader reader = Json.createReader(new StringReader(toJson(this.bom, false)));
 
-            return doc;
-        }
-        catch (GeneratorException e) {
+            return reader.readObject();
+        } catch (GeneratorException e) {
             return null;
         }
     }
diff --git a/src/main/java/org/cyclonedx/generators/json/OrderedJSONObjectFactory.java b/src/main/java/org/cyclonedx/generators/json/OrderedJSONObjectFactory.java
deleted file mode 100644
index f67f2382b..000000000
--- a/src/main/java/org/cyclonedx/generators/json/OrderedJSONObjectFactory.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.generators.json;
-
-import org.json.JSONObject;
-import java.lang.reflect.Field;
-import java.util.LinkedHashMap;
-
-public class OrderedJSONObjectFactory {
-
-    /**
-     * Changes the internal implementation of an unordered HashMap to an ordered
-     * LinkedHashMap. An unmodified JSONObject will be returned in the event of
-     * a reflection exception.
-     * @return a JSONObject
-     */
-    public static JSONObject create() {
-        try {
-            final Class<JSONObject> cls = JSONObject.class;
-            final JSONObject jsonObject = new JSONObject();
-            final Field field = cls.getDeclaredField("map");
-            field.setAccessible(true);
-            field.set(jsonObject, new LinkedHashMap<String, Object>());
-            field.setAccessible(false);
-            return jsonObject;
-        } catch (Exception e) {
-            return new JSONObject();
-        }
-    }
-}
diff --git a/src/main/java/org/cyclonedx/parsers/JsonParser.java b/src/main/java/org/cyclonedx/parsers/JsonParser.java
index 6b7fcf088..ea53f3b9e 100644
--- a/src/main/java/org/cyclonedx/parsers/JsonParser.java
+++ b/src/main/java/org/cyclonedx/parsers/JsonParser.java
@@ -19,21 +19,25 @@
 package org.cyclonedx.parsers;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.ValidationMessage;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.cyclonedx.CycloneDxSchema;
 import org.cyclonedx.exception.ParseException;
 import org.cyclonedx.model.Bom;
-import org.everit.json.schema.ValidationException;
-import org.json.JSONObject;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
-import java.net.URISyntaxException;
+import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
 
 /**
  * JsonParser is responsible for validating and parsing CycloneDX bill-of-material
@@ -210,8 +214,9 @@ public class JsonParser extends CycloneDxSchema implements Parser {
      * @throws IOException when errors are encountered
      * @since 3.0.0
      */
-    public List<ParseException> validate (final String bomString, final CycloneDxSchema.Version schemaVersion, boolean strict) throws IOException {
-        return validate(new JSONObject(bomString), schemaVersion, strict);
+    public List<ParseException> validate(final String bomString, final CycloneDxSchema.Version schemaVersion, boolean strict) throws IOException {
+        JsonReader reader = Json.createReader(new StringReader(bomString));
+        return validate(reader.readObject(), schemaVersion, strict);
     }
 
     /**
@@ -223,13 +228,15 @@ public class JsonParser extends CycloneDxSchema implements Parser {
      * @throws IOException when errors are encountered
      * @since 3.0.0
      */
-    public List<ParseException> validate (final JSONObject bomJson, final CycloneDxSchema.Version schemaVersion, boolean strict) throws IOException {
+    public List<ParseException> validate(final JsonObject bomJson, final CycloneDxSchema.Version schemaVersion, boolean strict) throws IOException {
         final List<ParseException> exceptions = new ArrayList<>();
-        try {
-            getJsonSchema(schemaVersion, strict).validate(bomJson);
-        } catch (ValidationException | URISyntaxException e) {
-            exceptions.add(new ParseException(e.getMessage(), e));
+
+        Set<ValidationMessage> errors = getJsonSchema(schemaVersion, strict, mapper).validate(mapper.readTree(bomJson.toString()));
+
+        for (ValidationMessage message: errors) {
+            exceptions.add(new ParseException(message.getMessage()));
         }
+
         return exceptions;
     }
 
diff --git a/src/main/java/org/cyclonedx/util/LicenseResolver.java b/src/main/java/org/cyclonedx/util/LicenseResolver.java
index 04d96d725..715a22696 100644
--- a/src/main/java/org/cyclonedx/util/LicenseResolver.java
+++ b/src/main/java/org/cyclonedx/util/LicenseResolver.java
@@ -18,44 +18,18 @@
  */
 package org.cyclonedx.util;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.io.IOUtils;
 import org.cyclonedx.model.License;
 import org.cyclonedx.model.LicenseChoice;
 import org.cyclonedx.model.AttachmentText;
-import org.json.JSONArray;
-import org.json.JSONObject;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.Base64;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 public final class LicenseResolver {
 
-    private static Map<String, List<String>> mappings = new HashMap<>();
-    static {
-        final InputStream is = LicenseResolver.class.getResourceAsStream("/license-mapping.json");
-        try {
-            final String jsonTxt = IOUtils.toString(is, "UTF-8");
-            final JSONArray json = new JSONArray(jsonTxt);
-            for (int i = 0; i < json.length(); i++) {
-                final JSONObject mapping = json.getJSONObject(i);
-                if (!mappings.containsKey(mapping.getString("exp"))) {
-                    mappings.put(mapping.getString("exp"), new ArrayList<>());
-                }
-                final List<String> names = mappings.get(mapping.getString("exp"));
-                for (int n = 0; n < mapping.getJSONArray("names").length(); n++) {
-                    names.add(mapping.getJSONArray("names").getString(n));
-                }
-                mappings.replace(mapping.getString("exp"), names);
-            }
-        } catch (IOException e) {
-            // This should be logged
-        }
-    }
-
     /**
      * Private constructor.
      */
@@ -66,29 +40,36 @@ public final class LicenseResolver {
      * Attempts to resolve the specified license string via SPDX license identifier and expression
      * parsing first. If SPDX resolution is not successful, the method will attempt fuzzy matching.
      * @param licenseString the license string to resolve
-     * @param includeLicenseText specifies is the resolved license will include the entire text of the license
      * @return a LicenseChoice object if resolution was successful, or null if unresolved
      */
-    public static LicenseChoice resolve(String licenseString, boolean includeLicenseText) {
-        try {
-            LicenseChoice licenseChoice = resolveSpdxLicenseString(licenseString, includeLicenseText);
-            if (licenseChoice == null) {
-                licenseChoice = resolveViaAlternativeMapping(licenseString, includeLicenseText);
-            }
-            return licenseChoice;
-        } catch (IOException e) {
-            return null;
-        }
+    public static LicenseChoice resolve(final String licenseString) {
+        return resolve(licenseString, true);
     }
 
     /**
      * Attempts to resolve the specified license string via SPDX license identifier and expression
      * parsing first. If SPDX resolution is not successful, the method will attempt fuzzy matching.
      * @param licenseString the license string to resolve
+     * @param includeLicenseText specifies is the resolved license will include the entire text of the license
      * @return a LicenseChoice object if resolution was successful, or null if unresolved
      */
-    public static LicenseChoice resolve(String licenseString) {
-        return resolve(licenseString, true);
+    public static LicenseChoice resolve(final String licenseString, final boolean includeLicenseText) {
+        final ObjectMapper mapper = new ObjectMapper();
+
+        return resolve(licenseString, includeLicenseText, mapper);
+    }
+
+    static LicenseChoice resolve(final String licenseString, final boolean includeLicenseText, final ObjectMapper mapper) {
+        try {
+            LicenseChoice licenseChoice = resolveLicenseString(licenseString, includeLicenseText, mapper);
+
+            if (licenseChoice == null) {
+                licenseChoice = resolveFuzzyMatching(licenseString, includeLicenseText, mapper);
+            }
+            return licenseChoice;
+        } catch (IOException ex) {
+            return null;
+        }
     }
 
     /**
@@ -96,40 +77,84 @@ public final class LicenseResolver {
      * return a LicenseChoice object.
      * @param licenseString the license string to resolve
      * @param includeLicenseText specifies is the resolved license will include the entire text of the license
+     * @param mapper is to provide a Jackson ObjectMapper
      * @return a LicenseChoice object if resolved, or null
      * @throws IOException an exception while parsing the license string
      */
-    private static LicenseChoice resolveSpdxLicenseString(String licenseString, boolean includeLicenseText) throws IOException {
+    private static LicenseChoice resolveLicenseString(String licenseString, boolean includeLicenseText, final ObjectMapper mapper)
+        throws IOException
+    {
         final InputStream is = LicenseResolver.class.getResourceAsStream("/licenses/licenses.json");
-        final String licenseStringModified = urlNormalize(licenseString);
-        final String jsonTxt = IOUtils.toString(is, "UTF-8");
-        final JSONObject json = new JSONObject(jsonTxt);
-        final JSONArray licenses = json.getJSONArray("licenses");
-        for (int i = 0; i < licenses.length(); i++) {
-            final JSONObject license = licenses.getJSONObject(i);
-            final boolean isDeprecatedLicenseId = license.getBoolean("isDeprecatedLicenseId");
-            final String licenseId = license.getString("licenseId");
-            final String name = license.getString("name");
-            final JSONArray seeAlso = license.optJSONArray("seeAlso");
-            final String primaryLicenseUrl = (seeAlso != null && seeAlso.length() > 0) ? seeAlso.getString(0) : null;
-            if (licenseString.trim().equalsIgnoreCase(licenseId)) {
-                return createLicenseChoice(licenseId, primaryLicenseUrl, isDeprecatedLicenseId, includeLicenseText);
-            } else if (licenseString.trim().equalsIgnoreCase(name)) {
-                return createLicenseChoice(licenseId, primaryLicenseUrl, isDeprecatedLicenseId, includeLicenseText);
-            } else {
-                if (isDeprecatedLicenseId) {
-                    continue;
+
+        final LicenseList licenses = mapper.readValue(is, LicenseList.class);
+
+        if (licenses != null && licenses.licenses != null && !licenses.licenses.isEmpty()) {
+            for (LicenseDetail licenseDetail : licenses.licenses) {
+
+                final String primaryLicenseUrl = (licenseDetail.seeAlso != null && !licenseDetail.seeAlso.isEmpty()) ? licenseDetail.seeAlso.get(0) : null;
+
+                if (licenseString.trim().equalsIgnoreCase(licenseDetail.licenseId)) {
+                    return createLicenseChoice(licenseDetail.licenseId, primaryLicenseUrl, licenseDetail.isDeprecatedLicenseId, includeLicenseText);
+                } else if (licenseString.trim().equalsIgnoreCase(licenseDetail.name)) {
+                    return createLicenseChoice(licenseDetail.licenseId, primaryLicenseUrl, licenseDetail.isDeprecatedLicenseId, includeLicenseText);
+                } else {
+
+                    if (licenseDetail.isDeprecatedLicenseId) {
+                        continue;
+                    }
+
+                    if (licenseDetail.seeAlso != null) {
+                        for (String url : licenseDetail.seeAlso) {
+                            if (url != null) {
+                                final String licenseStringModified = urlNormalize(licenseString);
+
+                                if (licenseStringModified.equalsIgnoreCase(urlNormalize(url))) {
+                                    return createLicenseChoice(licenseDetail.licenseId, url, licenseDetail.isDeprecatedLicenseId, includeLicenseText);
+                                }
+                            }
+                        }
+                    }
                 }
-                for (int s = 0; s < seeAlso.length(); s++) {
-                    final String url = seeAlso.getString(s);
-                    if (url != null) {
-                        if (licenseStringModified.equalsIgnoreCase(urlNormalize(url))) {
-                            return createLicenseChoice(licenseId, url, isDeprecatedLicenseId, includeLicenseText);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Attempts to perform high-confidence license resolution with unstructured text as input.
+     * @param licenseString the license string (not the actual license text)
+     * @param includeLicenseText specifies is the resolved license will include the entire text of the license
+     * @param mapper is to provide a Jackson ObjectMapper
+     * @return a LicenseChoice object if resolved, otherwise null
+     */
+    private static LicenseChoice resolveFuzzyMatching(final String licenseString, final boolean includeLicenseText, final ObjectMapper mapper) throws IOException {
+        if (licenseString == null) {
+            return null;
+        }
+
+        final InputStream is = LicenseResolver.class.getResourceAsStream("/license-mapping.json");
+
+        final SpdxLicenseMapping[] mappings = mapper.readValue(is, SpdxLicenseMapping[].class);
+
+        if (mappings != null) {
+            for(final SpdxLicenseMapping licenseMapping : mappings) {
+                if (licenseMapping.names != null && !licenseMapping.names.isEmpty()) {
+                    for (final String name : licenseMapping.names) {
+                        if (licenseString.equalsIgnoreCase(name)) {
+                            if (licenseMapping.exp.startsWith("(") && licenseMapping.exp.endsWith(")")) {
+                                final LicenseChoice lc = new LicenseChoice();
+                                lc.setExpression(licenseMapping.exp);
+                                return lc;
+                            } else {
+                                return createLicenseChoice(licenseMapping.exp, null, false, includeLicenseText);
+                            }
                         }
                     }
                 }
             }
         }
+
         return null;
     }
 
@@ -161,32 +186,26 @@ public final class LicenseResolver {
         return choice;
     }
 
-    /**
-     * Attempts to perform high-confidence license resolution with unstructured text as input.
-     * @param licenseString the license string (not the actual license text)
-     * @param includeLicenseText specifies is the resolved license will include the entire text of the license
-     * @return a LicenseChoice object if resolved, otherwise null
-     */
-    private static LicenseChoice resolveViaAlternativeMapping(String licenseString, boolean includeLicenseText) throws IOException {
-        if (licenseString == null) {
-            return null;
-        }
-        for (final Map.Entry<String, List<String>> mapping : mappings.entrySet()) {
-            final List<String> names = mapping.getValue();
-            if (names != null) {
-                for (final String name: names) {
-                    if (licenseString.equalsIgnoreCase(name)) {
-                        if (mapping.getKey().startsWith("(") && mapping.getKey().endsWith(")")) {
-                            final LicenseChoice lc = new LicenseChoice();
-                            lc.setExpression(mapping.getKey());
-                            return lc;
-                        } else {
-                            return createLicenseChoice(mapping.getKey(), null, false, includeLicenseText);
-                        }
-                    }
-                }
-            }
-        }
-        return null;
+    private static class LicenseDetail {
+        public String reference;
+        public boolean isDeprecatedLicenseId;
+        public String detailsUrl;
+        public String referenceNumber;
+        public String name;
+        public String licenseId;
+        public List<String> seeAlso;
+        public boolean isOsiApproved;
+        public boolean isFsfLibre;
+    }
+
+    private static class LicenseList {
+        public String licenseListVersion;
+        public List<LicenseDetail> licenses;
+        public String releaseDate;
+    }
+
+    private static class SpdxLicenseMapping{
+        public String exp;
+        public List<String> names;
     }
 }


Test patch:
diff --git a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
index 7d0ab322a..35cb4dac4 100644
--- a/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
+++ b/src/test/java/org/cyclonedx/BomJsonGeneratorTest.java
@@ -17,6 +17,8 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 
+import javax.json.JsonObject;
+
 public class BomJsonGeneratorTest {
 
     private File tempFile;
@@ -37,7 +39,6 @@ public class BomJsonGeneratorTest {
     public void schema12GenerationTest() throws Exception {
         Bom bom =  createCommonBom("/bom-1.2.xml");
         BomJsonGenerator generator = BomGeneratorFactory.createJson(Version.VERSION_12, bom);
-        generator.generate();
         Assert.assertTrue(generator instanceof BomJsonGenerator12);
         Assert.assertEquals(CycloneDxSchema.Version.VERSION_12, generator.getSchemaVersion());
         File file = writeToFile(generator.toJsonString());
@@ -45,6 +46,20 @@ public class BomJsonGeneratorTest {
         Assert.assertTrue(parser.isValid(file, CycloneDxSchema.Version.VERSION_12, true));
     }
 
+    @Test
+    public void schema12JsonObjectGenerationTest() throws Exception {
+        Bom bom = createCommonBom("/bom-1.2.xml");
+        BomJsonGenerator generator = BomGeneratorFactory.createJson(Version.VERSION_12, bom);
+        JsonObject obj = generator.toJsonObject();
+        Assert.assertNotNull(obj);
+        Assert.assertEquals("CycloneDX", obj.getString("bomFormat"));
+        Assert.assertEquals("1.2", obj.getString("specVersion"));
+        Assert.assertEquals("urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", obj.getString("serialNumber"));
+        Assert.assertEquals(1, obj.getInt("version"));
+        Assert.assertEquals(6, obj.getJsonObject("metadata").size());
+        Assert.assertEquals(3, obj.getJsonArray("components").size());
+    }
+
     private File writeToFile(String jsonString) throws Exception {
         try (FileWriter writer = new FileWriter(tempFile.getAbsolutePath())) {
             writer.write(jsonString);