class Checklist implements JsonObjectConvertible {

    public static Checklist fromJson(final @NotNull String moduleName,
                                     final @Nullable URL manifestUrl,
                                     final @NotNull JsonObject json) {
        LOGGER.trace("[fromJson] module: {}, manifestUrl: {}, json: {}", moduleName, manifestUrl, json);
        Builder builder = new Builder(moduleName);
        final JsonKeys jsonKeys = keys();
        if (hasNonNull(json, jsonKeys.name())) {
            builder.withName(json.getString(jsonKeys.name()));
        }

        if (manifestUrl != null && manifestUrl.toExternalForm().endsWith(JarFile.MANIFEST_NAME)) {
            ofNullable(json.getJsonArray(jsonKeys.cndNames()))
                    .filter(Util.traceFilter(LOGGER, "[fromJson#cndNames] cndNames: {}"))
                    .map(cndNames -> JavaxJson.unwrapArray(cndNames).stream()
                            .map(String::valueOf)
                            .collect(Collectors.toList()))
                    .map(names -> Util.resolveManifestResources(manifestUrl, names))
                    .ifPresent(builder::withCndUrls);
        }

        builder.withCndUrls(parseFromArray(
                optArray(json, jsonKeys.cndUrls()).orElse(JsonValue.EMPTY_JSON_ARRAY), URL::new,
                (element, error) -> LOGGER.debug("[fromJson#cndUrls] (URL ERROR) {}", error.getMessage())));

        final List<JcrNs> jcrNsList = new ArrayList<>();
        optArray(json, jsonKeys.jcrNamespaces()).ifPresent(jsonArray -> {
            jcrNsList.addAll(JavaxJson.mapArrayOfObjects(jsonArray, JcrNs::fromJson));
            builder.withJcrNamespaces(jcrNsList);
        });
        optObject(json, jsonKeys.jcrNodetypes()).ifPresent(jsonObject -> {
            builder.withJcrNodetypes(
                    JsonCnd.getQTypesFromJson(jsonObject,
                            JsonCnd.toNamespaceMapping(jcrNsList)));
        });
        if (json.containsKey(jsonKeys.jcrPrivileges())) {
            builder.withJcrPrivileges(
                    JsonCnd.getPrivilegesFromJson(json.get(jsonKeys.jcrPrivileges()),
                            JsonCnd.toNamespaceMapping(jcrNsList)));
        }
        optArray(json, jsonKeys.forcedRoots()).ifPresent(jsonArray -> {
            builder.withForcedRoots(JavaxJson.mapArrayOfObjects(jsonArray, ForcedRoot::fromJson));
        });
        optArray(json, jsonKeys.checks()).ifPresent(jsonArray -> {
            builder.withChecks(JavaxJson.mapArrayOfObjects(jsonArray, CheckSpec::fromJson));
        });
        final Checklist checklist = builder.build(json);
        LOGGER.trace("[fromJson] builder.build(): {}", checklist);
        return checklist;
    }

      Checklist(final @Nullable JsonObject originalJson,
              final @Nullable String moduleName,
              final @Nullable String name,
              final @NotNull List<URL> cndUrls,
              final @NotNull List<JcrNs> jcrNamespaces,
              final @NotNull List<QNodeTypeDefinition> jcrNodetypes,
              final @NotNull List<PrivilegeDefinition> jcrPrivileges,
              final @NotNull List<ForcedRoot> forcedRoots,
              final @NotNull List<CheckSpec.ImmutableSpec> checks,
              final @NotNull List<String> repoInits);

}