diff --git a/pom.xml b/pom.xml index 01092c7..9ca91e6 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.umutayb Utilities - 1.7.1 + 1.7.2-SNAPSHOT jar Java-Utilities @@ -190,6 +190,13 @@ 3.0.1 + + + com.fasterxml.jackson.module + jackson-module-jsonSchema + 2.18.3 + + @@ -288,7 +295,7 @@ --pinentry-mode loopback - false + true diff --git a/src/main/java/api_assured/ServiceGenerator.java b/src/main/java/api_assured/ServiceGenerator.java index 0feea32..b039ea8 100644 --- a/src/main/java/api_assured/ServiceGenerator.java +++ b/src/main/java/api_assured/ServiceGenerator.java @@ -1,5 +1,10 @@ package api_assured; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.json.JsonWriteFeature; +import com.fasterxml.jackson.databind.SerializationFeature; import context.ContextStore; import okhttp3.Headers; import okhttp3.OkHttpClient; @@ -199,9 +204,9 @@ public S generate(Class serviceClass) { @SuppressWarnings("deprecation") Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) + .addConverterFactory(JacksonConverterFactory.create(mapper)) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .addConverterFactory(JacksonConverterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(SimpleXmlConverterFactory.create()) //Deprecated .addConverterFactory(MoshiConverterFactory.create()) diff --git a/src/main/java/utils/MappingUtilities.java b/src/main/java/utils/MappingUtilities.java index be26492..09f266f 100644 --- a/src/main/java/utils/MappingUtilities.java +++ b/src/main/java/utils/MappingUtilities.java @@ -3,19 +3,32 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.module.jsonSchema.JsonSchema; +import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator; +/** + * Utility class for working with JSON mappings and generating JSON schemas. + * Provides methods for serializing and deserializing Java objects to and from JSON. + * It also includes functionality to generate JSON schema representations. + */ public class MappingUtilities { /** - * Visibility settings are applied to allow the mapper to access and serialize/deserialize all fields, while ignoring getters, setters, and creators. - * The FAIL_ON_EMPTY_BEANS serialization feature is disabled, which prevents the mapper from throwing an exception when encountering empty beans (objects without any properties). + * Utility class for handling JSON serialization and deserialization. + * Configures an {@link ObjectMapper} for serializing and deserializing Java objects to and from JSON. */ public static class Json { + + // An ObjectMapper configured for JSON operations public static ObjectMapper mapper = new ObjectMapper(); static { + // Configure ObjectMapper settings mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); mapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE); mapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE); @@ -26,11 +39,11 @@ public static class Json { } /** - * Converts the given object into its JSON string representation in a pretty-printed format. + * Converts the given object into a JSON string representation with pretty-print formatting. * * @param The type of the object to be converted to JSON. * @param body The object to be converted to JSON. - * @return The JSON string representation of the given object in a pretty-printed format. + * @return The JSON string representation of the given object in pretty-printed format. */ public static String getJsonStringFor(T body) { return mapper.valueToTree(body).toPrettyString(); @@ -42,13 +55,14 @@ public static String getJsonStringFor(T body) { * @param The type of the object to be converted to JSON. * @param body The object to be converted to JSON. * @return The JSON string representation of the given object in a pretty-printed format. + * @throws JsonProcessingException If a JSON processing error occurs. */ public static String getJsonString(T body) throws JsonProcessingException { return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(body); } /** - * Converts a Json string representation of an object into a Java object of the specified type. + * Converts a JSON string representation of an object into a Java object of the specified type. * * @param The type of the object. * @param jsonString The JSON string representation of the object. @@ -59,5 +73,116 @@ public static String getJsonString(T body) throws JsonProcessingException { public static T fromJsonString(String jsonString, Class model) throws JsonProcessingException { return MappingUtilities.Json.mapper.readValue(jsonString, model); } + + /** + * Utility class for generating JSON schema representations. + */ + public static class Schema { + + /** + * Recursively sets the ID of all nested JSON schemas to null. + * This method traverses through object schemas and array schemas, setting their ID properties to null. + * + * @param schema The root JsonSchema object to be processed. + /** + * Recursively sets the ID of all nested JSON schemas to null. + * This method traverses through object schemas and array schemas, setting their ID properties to null. + * + * @param schema The root JsonSchema object to be processed. + * @return The input JsonSchema with all nested schemas' IDs set to null. + */ + public static JsonSchema setIdNull(JsonSchema schema) { + if (schema.isObjectSchema()) { + for (String propertyKey : schema.asObjectSchema().getProperties().keySet()) { + JsonSchema childSchema = schema.asObjectSchema().getProperties().get(propertyKey); + if (childSchema.isObjectSchema()) + setIdNull(childSchema); + + if (childSchema.isArraySchema()) + setIdNull(childSchema.asArraySchema().getItems().asSingleItems().getSchema()); + + schema.setId(null); + schema.set$ref(null); + } + } + return schema; + } + + /** + * Generates a JSON schema for the given class, with the option to specify required fields. + * This method uses the Jackson library to generate the schema and customize it based on the provided required fields. + * It sets the ID of the schema and its nested schemas to null and adds the required fields to the schema's "required" property. + * + * @param clazz The class for which the JSON schema should be generated. + * @param requiredFields A varargs array of field names that should be marked as "required" in the schema. + * @return A JsonNode representing the generated schema, or null if an exception occurs during generation. + */ + public static JsonNode getJsonNodeFor(Class clazz, String... requiredFields) { + JsonSchema schema = generateSchema(clazz); + assert schema != null; + schema.setId(null); + return addRequiredFields(schema, requiredFields); + } + + /** + * Generates a JSON schema for the given class, with the option to specify required fields. + * This method uses the Jackson library to generate the schema. + * It sets the ID of the schema and its nested schemas to null. + * + * @param clazz The class for which the JSON schema should be generated. + * @return A JsonNode representing the generated schema, or null if an exception occurs during generation. + */ + public static JsonNode getJsonNodeFor(Class clazz) { + JsonSchema schema = generateSchema(clazz); + assert schema != null; + schema.setId(null); + return mapper.valueToTree(schema); + } + + /** + * Generates a JSON schema for the given class, with the option to specify required fields. + * This method uses the Jackson library to generate the schema and customize it based on the provided required fields. + * It sets the ID of the schema and its nested schemas to null and adds the required fields to the schema's "required" property. + * + * @param clazz The class for which the JSON schema should be generated. + * @return A JsonNode representing the generated schema, or null if an exception occurs during generation. + */ + public static JsonSchema generateSchema(Class clazz) { + try { + JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(new ObjectMapper()); + return setIdNull(schemaGen.generateSchema(clazz)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Adds the specified required fields to the "required" property of a JSON schema. + * This method adds each field name from the provided array to the "required" property of the schema. + * + * @param schema The JSON schema to modify. + * @param requiredFields An array of field names that should be marked as required in the schema. + * @return + */ + private static JsonNode addRequiredFields(JsonSchema schema, String[] requiredFields) { + JsonNode schemaNode = mapper.valueToTree(schema); + + if (!(schemaNode instanceof ObjectNode root)) { + throw new IllegalArgumentException("Schema must be an ObjectNode"); + } + + ArrayNode required = root.withArray("required"); + + for (String fieldName : requiredFields) { + required.add(fieldName); + } + + // Convert the modified JsonNode back to JsonSchema + return mapper.valueToTree(root); + } + + } } } + diff --git a/src/test/java/AppTest.java b/src/test/java/AppTest.java index 84b17a2..90750fd 100644 --- a/src/test/java/AppTest.java +++ b/src/test/java/AppTest.java @@ -1,5 +1,6 @@ import collections.Pair; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonObject; import context.ContextStore; import enums.ZoneIds; @@ -280,4 +281,14 @@ public void partitionCountTest() { ); printer.success("The partitionCountTest() test pass!"); } + + @Test + public void jsonSchemaTest() { + JsonNode petSchema = MappingUtilities.Json.Schema.getJsonNodeFor(Pet.class); + Assert.assertEquals( + "Generated json schema did not match the expected one!", + "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\"},\"category\":{\"type\":\"any\"},\"name\":{\"type\":\"string\"},\"photoUrls\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"tags\":{\"type\":\"array\",\"items\":{\"type\":\"any\"}},\"status\":{\"type\":\"string\"}}}", + petSchema.toString() + ); + } }