Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.umutayb</groupId>
<artifactId>Utilities</artifactId>
<version>1.7.1</version>
<version>1.7.2-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Java-Utilities</name>
Expand Down Expand Up @@ -190,6 +190,13 @@
<version>3.0.1</version>
</dependency>

<!-- Jackson Schema -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jsonSchema</artifactId>
<version>2.18.3</version>
</dependency>

</dependencies>

<scm>
Expand Down Expand Up @@ -288,7 +295,7 @@
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
<skip>false</skip>
<skip>true</skip>
</configuration>
</execution>
</executions>
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/api_assured/ServiceGenerator.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -199,9 +204,9 @@ public <S> S generate(Class<S> 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())
Expand Down
135 changes: 130 additions & 5 deletions src/main/java/utils/MappingUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 <T> 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 <T> String getJsonStringFor(T body) {
return mapper.valueToTree(body).toPrettyString();
Expand All @@ -42,13 +55,14 @@ public static <T> String getJsonStringFor(T body) {
* @param <T> 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 <T> 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 <T> The type of the object.
* @param jsonString The JSON string representation of the object.
Expand All @@ -59,5 +73,116 @@ public static <T> String getJsonString(T body) throws JsonProcessingException {
public static <T> T fromJsonString(String jsonString, Class<T> 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);
}

}
}
}

11 changes: 11 additions & 0 deletions src/test/java/AppTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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()
);
}
}