Skip to content

Commit 710d6b5

Browse files
authored
Merge pull request #111 from Umutayb/json-schemas
Added json schema generator!
2 parents 3da792c + 75b247b commit 710d6b5

File tree

4 files changed

+156
-8
lines changed

4 files changed

+156
-8
lines changed

pom.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.github.umutayb</groupId>
88
<artifactId>Utilities</artifactId>
9-
<version>1.7.1</version>
9+
<version>1.7.2-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<name>Java-Utilities</name>
@@ -190,6 +190,13 @@
190190
<version>3.0.1</version>
191191
</dependency>
192192

193+
<!-- Jackson Schema -->
194+
<dependency>
195+
<groupId>com.fasterxml.jackson.module</groupId>
196+
<artifactId>jackson-module-jsonSchema</artifactId>
197+
<version>2.18.3</version>
198+
</dependency>
199+
193200
</dependencies>
194201

195202
<scm>
@@ -288,7 +295,7 @@
288295
<arg>--pinentry-mode</arg>
289296
<arg>loopback</arg>
290297
</gpgArguments>
291-
<skip>false</skip>
298+
<skip>true</skip>
292299
</configuration>
293300
</execution>
294301
</executions>

src/main/java/api_assured/ServiceGenerator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package api_assured;
22

3+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4+
import com.fasterxml.jackson.annotation.PropertyAccessor;
5+
import com.fasterxml.jackson.core.JsonGenerator;
6+
import com.fasterxml.jackson.core.json.JsonWriteFeature;
7+
import com.fasterxml.jackson.databind.SerializationFeature;
38
import context.ContextStore;
49
import okhttp3.Headers;
510
import okhttp3.OkHttpClient;
@@ -199,9 +204,9 @@ public <S> S generate(Class<S> serviceClass) {
199204
@SuppressWarnings("deprecation")
200205
Retrofit retrofit = new Retrofit.Builder()
201206
.baseUrl(BASE_URL)
207+
.addConverterFactory(JacksonConverterFactory.create(mapper))
202208
.addConverterFactory(GsonConverterFactory.create())
203209
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
204-
.addConverterFactory(JacksonConverterFactory.create())
205210
.addConverterFactory(ScalarsConverterFactory.create())
206211
.addConverterFactory(SimpleXmlConverterFactory.create()) //Deprecated
207212
.addConverterFactory(MoshiConverterFactory.create())

src/main/java/utils/MappingUtilities.java

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,32 @@
33
import com.fasterxml.jackson.annotation.JsonAutoDetect;
44
import com.fasterxml.jackson.annotation.PropertyAccessor;
55
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.JsonNode;
67
import com.fasterxml.jackson.databind.ObjectMapper;
78
import com.fasterxml.jackson.databind.SerializationFeature;
9+
import com.fasterxml.jackson.databind.node.ArrayNode;
10+
import com.fasterxml.jackson.databind.node.ObjectNode;
11+
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
12+
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
813

14+
/**
15+
* Utility class for working with JSON mappings and generating JSON schemas.
16+
* Provides methods for serializing and deserializing Java objects to and from JSON.
17+
* It also includes functionality to generate JSON schema representations.
18+
*/
919
public class MappingUtilities {
1020

1121
/**
12-
* Visibility settings are applied to allow the mapper to access and serialize/deserialize all fields, while ignoring getters, setters, and creators.
13-
* 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).
22+
* Utility class for handling JSON serialization and deserialization.
23+
* Configures an {@link ObjectMapper} for serializing and deserializing Java objects to and from JSON.
1424
*/
1525
public static class Json {
26+
27+
// An ObjectMapper configured for JSON operations
1628
public static ObjectMapper mapper = new ObjectMapper();
1729

1830
static {
31+
// Configure ObjectMapper settings
1932
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
2033
mapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
2134
mapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE);
@@ -26,11 +39,11 @@ public static class Json {
2639
}
2740

2841
/**
29-
* Converts the given object into its JSON string representation in a pretty-printed format.
42+
* Converts the given object into a JSON string representation with pretty-print formatting.
3043
*
3144
* @param <T> The type of the object to be converted to JSON.
3245
* @param body The object to be converted to JSON.
33-
* @return The JSON string representation of the given object in a pretty-printed format.
46+
* @return The JSON string representation of the given object in pretty-printed format.
3447
*/
3548
public static <T> String getJsonStringFor(T body) {
3649
return mapper.valueToTree(body).toPrettyString();
@@ -42,13 +55,14 @@ public static <T> String getJsonStringFor(T body) {
4255
* @param <T> The type of the object to be converted to JSON.
4356
* @param body The object to be converted to JSON.
4457
* @return The JSON string representation of the given object in a pretty-printed format.
58+
* @throws JsonProcessingException If a JSON processing error occurs.
4559
*/
4660
public static <T> String getJsonString(T body) throws JsonProcessingException {
4761
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
4862
}
4963

5064
/**
51-
* Converts a Json string representation of an object into a Java object of the specified type.
65+
* Converts a JSON string representation of an object into a Java object of the specified type.
5266
*
5367
* @param <T> The type of the object.
5468
* @param jsonString The JSON string representation of the object.
@@ -59,5 +73,116 @@ public static <T> String getJsonString(T body) throws JsonProcessingException {
5973
public static <T> T fromJsonString(String jsonString, Class<T> model) throws JsonProcessingException {
6074
return MappingUtilities.Json.mapper.readValue(jsonString, model);
6175
}
76+
77+
/**
78+
* Utility class for generating JSON schema representations.
79+
*/
80+
public static class Schema {
81+
82+
/**
83+
* Recursively sets the ID of all nested JSON schemas to null.
84+
* This method traverses through object schemas and array schemas, setting their ID properties to null.
85+
*
86+
* @param schema The root JsonSchema object to be processed.
87+
/**
88+
* Recursively sets the ID of all nested JSON schemas to null.
89+
* This method traverses through object schemas and array schemas, setting their ID properties to null.
90+
*
91+
* @param schema The root JsonSchema object to be processed.
92+
* @return The input JsonSchema with all nested schemas' IDs set to null.
93+
*/
94+
public static JsonSchema setIdNull(JsonSchema schema) {
95+
if (schema.isObjectSchema()) {
96+
for (String propertyKey : schema.asObjectSchema().getProperties().keySet()) {
97+
JsonSchema childSchema = schema.asObjectSchema().getProperties().get(propertyKey);
98+
if (childSchema.isObjectSchema())
99+
setIdNull(childSchema);
100+
101+
if (childSchema.isArraySchema())
102+
setIdNull(childSchema.asArraySchema().getItems().asSingleItems().getSchema());
103+
104+
schema.setId(null);
105+
schema.set$ref(null);
106+
}
107+
}
108+
return schema;
109+
}
110+
111+
/**
112+
* Generates a JSON schema for the given class, with the option to specify required fields.
113+
* This method uses the Jackson library to generate the schema and customize it based on the provided required fields.
114+
* It sets the ID of the schema and its nested schemas to null and adds the required fields to the schema's "required" property.
115+
*
116+
* @param clazz The class for which the JSON schema should be generated.
117+
* @param requiredFields A varargs array of field names that should be marked as "required" in the schema.
118+
* @return A JsonNode representing the generated schema, or null if an exception occurs during generation.
119+
*/
120+
public static JsonNode getJsonNodeFor(Class<?> clazz, String... requiredFields) {
121+
JsonSchema schema = generateSchema(clazz);
122+
assert schema != null;
123+
schema.setId(null);
124+
return addRequiredFields(schema, requiredFields);
125+
}
126+
127+
/**
128+
* Generates a JSON schema for the given class, with the option to specify required fields.
129+
* This method uses the Jackson library to generate the schema.
130+
* It sets the ID of the schema and its nested schemas to null.
131+
*
132+
* @param clazz The class for which the JSON schema should be generated.
133+
* @return A JsonNode representing the generated schema, or null if an exception occurs during generation.
134+
*/
135+
public static JsonNode getJsonNodeFor(Class<?> clazz) {
136+
JsonSchema schema = generateSchema(clazz);
137+
assert schema != null;
138+
schema.setId(null);
139+
return mapper.valueToTree(schema);
140+
}
141+
142+
/**
143+
* Generates a JSON schema for the given class, with the option to specify required fields.
144+
* This method uses the Jackson library to generate the schema and customize it based on the provided required fields.
145+
* It sets the ID of the schema and its nested schemas to null and adds the required fields to the schema's "required" property.
146+
*
147+
* @param clazz The class for which the JSON schema should be generated.
148+
* @return A JsonNode representing the generated schema, or null if an exception occurs during generation.
149+
*/
150+
public static JsonSchema generateSchema(Class<?> clazz) {
151+
try {
152+
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(new ObjectMapper());
153+
return setIdNull(schemaGen.generateSchema(clazz));
154+
} catch (Exception e) {
155+
e.printStackTrace();
156+
return null;
157+
}
158+
}
159+
160+
/**
161+
* Adds the specified required fields to the "required" property of a JSON schema.
162+
* This method adds each field name from the provided array to the "required" property of the schema.
163+
*
164+
* @param schema The JSON schema to modify.
165+
* @param requiredFields An array of field names that should be marked as required in the schema.
166+
* @return
167+
*/
168+
private static JsonNode addRequiredFields(JsonSchema schema, String[] requiredFields) {
169+
JsonNode schemaNode = mapper.valueToTree(schema);
170+
171+
if (!(schemaNode instanceof ObjectNode root)) {
172+
throw new IllegalArgumentException("Schema must be an ObjectNode");
173+
}
174+
175+
ArrayNode required = root.withArray("required");
176+
177+
for (String fieldName : requiredFields) {
178+
required.add(fieldName);
179+
}
180+
181+
// Convert the modified JsonNode back to JsonSchema
182+
return mapper.valueToTree(root);
183+
}
184+
185+
}
62186
}
63187
}
188+

src/test/java/AppTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import collections.Pair;
22
import com.fasterxml.jackson.core.JsonProcessingException;
3+
import com.fasterxml.jackson.databind.JsonNode;
34
import com.google.gson.JsonObject;
45
import context.ContextStore;
56
import enums.ZoneIds;
@@ -280,4 +281,14 @@ public void partitionCountTest() {
280281
);
281282
printer.success("The partitionCountTest() test pass!");
282283
}
284+
285+
@Test
286+
public void jsonSchemaTest() {
287+
JsonNode petSchema = MappingUtilities.Json.Schema.getJsonNodeFor(Pet.class);
288+
Assert.assertEquals(
289+
"Generated json schema did not match the expected one!",
290+
"{\"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\"}}}",
291+
petSchema.toString()
292+
);
293+
}
283294
}

0 commit comments

Comments
 (0)