@TestFactory
    @Execution(ExecutionMode.SAME_THREAD)
    Stream<DynamicTest> testJsonAnnotations() {
        return buildRelatedResourceTests(clazz -> {

            if (!Modifier.isInterface(clazz.getModifiers())
                    && !Modifier.isAbstract(clazz.getModifiers())
                    && !clazz.isEnum()) {
                final boolean hasJsonInclude = clazz.getAnnotation(JsonInclude.class) != null;
                final boolean hasJsonPropertyOrder = clazz.getAnnotation(JsonPropertyOrder.class) != null;
                final AtomicInteger jsonCreatorCount = new AtomicInteger(0);
                final Set<String> fieldsWithoutAnnotations = new HashSet<>();
                final Set<String> methodsWithAnnotations = new HashSet<>();
                final Set<String> constructorPropNames = new HashSet<>();
                final Set<String> fieldPropNames;

                // If we only have a default constructor then we don't want the JsonCreator annotation to be
                // used as it breaks RestyGWT.
                final AtomicBoolean singleEmptyConstructor = new AtomicBoolean(true);

                for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
                    final JsonCreator jsonCreator = constructor.getAnnotation(JsonCreator.class);
                    if (jsonCreator != null) {
                        jsonCreatorCount.incrementAndGet();
                        constructorPropNames.addAll(getConstructorPropNames(constructor));
                    }
                    if (constructor.getParameterCount() > 0) {
                        singleEmptyConstructor.set(false);
                    }
                }

                fieldPropNames = getAllFields(clazz).stream()
                        .filter(field -> field.getDeclaredAnnotation(JsonIgnore.class) == null
                                && field.getDeclaredAnnotation(JsonProperty.class) != null)
                        .map(field -> {
                            final JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
                            if (!jsonProperty.value().isEmpty()) {
                                return jsonProperty.value();
                            } else {
                                return field.getName();
                            }
                        })
                        .collect(Collectors.toSet());

//                    LOGGER.info("{}", fieldPropNames);

                final Field[] fields = clazz.getDeclaredFields();
                for (final Field field : fields) {
                    final JsonIgnore jsonIgnore = field.getDeclaredAnnotation(JsonIgnore.class);
                    final JsonProperty jsonProperty = field.getDeclaredAnnotation(JsonProperty.class);
                    if (jsonIgnore == null
                            && jsonProperty == null
                            && !Modifier.isStatic(field.getModifiers())) {
                        String fieldName = field.getName();
                        fieldsWithoutAnnotations.add(fieldName);
                    }
                }

                final Method[] methods = clazz.getDeclaredMethods();
                for (final Method method : methods) {
                    final JsonProperty jsonProperty = method.getDeclaredAnnotation(JsonProperty.class);
                    if (jsonProperty != null) {
                        methodsWithAnnotations.add(method.getName());
                    }
                }

                SoftAssertions.assertSoftly(softly -> {
                    softly.assertThat(constructorPropNames)
                            .describedAs("%s - JsonProperties defined in the constructor must have a " +
                                    "corresponding JsonProperty on the field.", clazz.getName())
                            .containsExactlyInAnyOrderElementsOf(fieldPropNames);

                    softly.assertThat(hasJsonInclude)
                            .describedAs("%s - Missing JsonInclude annotation.", clazz.getName())
                            .isTrue();
//                                softly.assertThat(hasJsonPropertyOrder)
//                                        .withFailMessage("No JsonPropertyOrder")
//                                        .isTrue();
                    if (singleEmptyConstructor.get()) {
                        // If we only have a default constructor then we don't want the JsonCreator annotation to be
                        // used as it breaks RestyGWT.
                        softly.assertThat(jsonCreatorCount)
                                .withFailMessage(
                                        "%s - Should have no JsonCreator on default constructor, found %s",
                                        clazz.getName(), jsonCreatorCount.get())
                                .hasValue(0);
                    } else {
                        softly.assertThat(jsonCreatorCount)
                                .withFailMessage(
                                        "%s - Should have exactly one JsonCreator, found %s",
                                        clazz.getName(), jsonCreatorCount.get())
                                .hasValue(1);
                    }
                    softly.assertThat(fieldsWithoutAnnotations)
                            .withFailMessage(
                                    "%s - Fields without annotations: %s",
                                    clazz.getName(), fieldsWithoutAnnotations)
                            .isEmpty();
                    softly.assertThat(methodsWithAnnotations)
                            .withFailMessage(
                                    "%s - Methods with annotations: %s",
                                    clazz.getName(), methodsWithAnnotations)
                            .isEmpty();
                });
            }
        });
    }
Test that classes that will be subject to JSON serialisation have the full complement of JSON annotations.
stroom-app/src/test/java/stroom/importexport/TestJsonSerialisation.java