Skip to content

Commit d371393

Browse files
committed
support for mutually exclusive fields
1 parent 038ad8a commit d371393

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

src/main/java/org/dynapi/jsonschema/gen/AnnotationParser.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.dynapi.jsonschema.gen.annotations.*;
44
import org.dynapi.jsonschema.gen.schema.*;
5+
import org.json.JSONObject;
56

67
import java.lang.reflect.Field;
78
import java.util.*;
@@ -32,6 +33,8 @@ class AnnotationParser {
3233
throw new RuntimeException("Unknown properties to hide: " + String.join(", ", diff));
3334
}
3435

36+
List<Set<String>> mutuallyExclusivesGroups = new ArrayList<>();
37+
3538
for (Field field : clazz.getDeclaredFields()) {
3639
if (hiddenPropertiesNames.contains(field.getName()) || field.isAnnotationPresent(Hidden.class)) continue;
3740

@@ -57,9 +60,41 @@ class AnnotationParser {
5760
jsonSchema.requiredIf(ifField, requiredIf.value());
5861
}
5962

63+
// finds a mutually exclusive group and adds, or creates a new one
64+
MutuallyExclusiveWith mutuallyExclusiveWith = field.getAnnotation(MutuallyExclusiveWith.class);
65+
if (mutuallyExclusiveWith != null) {
66+
Set<String> group = new HashSet<>();
67+
group.add(field.getName());
68+
group.addAll(Arrays.asList(mutuallyExclusiveWith.value()));
69+
Optional<Set<String>> existingGroup = mutuallyExclusivesGroups.stream().filter(g -> !Collections.disjoint(g, group)).findFirst();
70+
if (existingGroup.isPresent()) {
71+
existingGroup.get().addAll(group);
72+
} else {
73+
mutuallyExclusivesGroups.add(group);
74+
}
75+
}
76+
6077
jsonSchema.addProperty(field.getName(), fieldSchema);
6178
}
6279

80+
// weird way of adding mutually exclusive fields
81+
if (!mutuallyExclusivesGroups.isEmpty()) {
82+
AllOf allOf = new AllOf();
83+
for (Set<String> group : mutuallyExclusivesGroups) {
84+
OneOf oneOf = new OneOf();
85+
for (String fieldName : group) {
86+
Set<String> otherFields = new HashSet<>(group);
87+
otherFields.remove(fieldName);
88+
BackdoorSchema backdoor = new BackdoorSchema()
89+
.setDirectly("required", List.of(fieldName))
90+
.setDirectly("not", new JSONObject().put("required", otherFields));
91+
oneOf.addSchema(backdoor);
92+
}
93+
allOf.addSchema(oneOf);
94+
}
95+
BackdoorSchema.addExtraSchemaDataFromTo(allOf, jsonSchema);
96+
}
97+
6398
return jsonSchema;
6499
}
65100

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.dynapi.jsonschema.gen.annotations;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* marks a field as mutually exclusive with another one.
7+
*/
8+
@Documented
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target(ElementType.FIELD)
11+
public @interface MutuallyExclusiveWith {
12+
String[] value() default {};
13+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.dynapi.jsonschema.gen.schema;
2+
3+
import org.json.JSONObject;
4+
5+
/**
6+
* <b>for internal usage of {@code org.dynapi:json-schema-gen} only</b>
7+
*/
8+
public class BackdoorSchema extends Schema<BackdoorSchema, Void> {
9+
10+
public BackdoorSchema setDirectly(String option, Object value) {
11+
this.options.put(option, value);
12+
return this;
13+
}
14+
15+
@Override
16+
protected JSONObject extraSchemaData() {
17+
return new JSONObject();
18+
}
19+
20+
public static void addExtraSchemaDataFromTo(Schema<?, ?> from, Schema<?, ?> to) {
21+
JSONObject extraData = from.extraSchemaData();
22+
for (String key : extraData.keySet())
23+
to.options.put(key, extraData.get(key));
24+
}
25+
}

0 commit comments

Comments
 (0)