Ключевые слова пользовательских свойств в схеме JSON с использованием аннотаций Джексона - PullRequest
0 голосов
/ 30 сентября 2019

Я хотел бы создать пользовательские ключевые слова свойств, которые будут предварительно заданы в схеме JSON, сгенерированной с помощью Джексона. Это будет похоже на то, что делает аннотация JsonPropertyDescription, но для пользовательских аннотаций и ключевых слов. Например, я хотел бы иметь возможность аннотировать свойство с помощью пользовательской аннотации JsonPropertyLabel, которая добавит ключевое слово «label» в описание свойства.

Мне удалось сделать аналогичную вещь с помощью пользовательской JsonSchemaи JsonSchemaFactory, как это:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.customProperties.ValidationSchemaFactoryWrapper;
import com.fasterxml.jackson.module.jsonSchema.factories.FormatVisitorFactory;
import com.fasterxml.jackson.module.jsonSchema.factories.JsonSchemaFactory;
import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper;
import com.fasterxml.jackson.module.jsonSchema.factories.VisitorContext;
import com.fasterxml.jackson.module.jsonSchema.factories.WrapperFactory;
import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema;
import com.fasterxml.jackson.annotation.JacksonAnnotation;
import com.fasterxml.jackson.module.jsonSchema.validation.AnnotationConstraintResolver;


public class SchemaUtils {

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @JacksonAnnotation
    public @interface JsonPropertyLabel {
        String value();
    }

    public static String getPrettyClassSchema(Class type) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(ConfigurationUtils.getJsonSchema(type, objectMapper));
    }

    static JsonSchema getJsonSchema(Class type, ObjectMapper objectMapper) throws JsonMappingException {
        SchemaFactoryWrapper schemaFactoryWrapper = new CustomJsonSchemaFactoryWrapper();
        objectMapper.acceptJsonFormatVisitor(objectMapper.constructType(type), schemaFactoryWrapper); 
        return schemaFactoryWrapper.finalSchema();
    }

    static class CustomJsonSchemaFactoryWrapper extends ValidationSchemaFactoryWrapper {
    private static class SchemaFactoryWrapperFactory extends WrapperFactory {
      private SchemaFactoryWrapperFactory() {
      }

      public SchemaFactoryWrapper getWrapper(SerializerProvider p) {
        return this.getWrapper(p, null);
      }

      public SchemaFactoryWrapper getWrapper(SerializerProvider p, VisitorContext rvc) {
        SchemaFactoryWrapper wrapper = new CustomJsonSchemaFactoryWrapper();
        wrapper.setProvider(p);
        if(rvc != null) {
          wrapper.setVisitorContext(rvc);
        }
        return wrapper;
      }
    }

    public CustomJsonSchemaFactoryWrapper() {
      super(new AnnotationConstraintResolver());
      schemaProvider = new CustomJsonSchemaFactory();
      visitorFactory = new FormatVisitorFactory(new SchemaFactoryWrapperFactory());
    }

    static class CustomJsonSchemaFactory extends JsonSchemaFactory {
      @Override
      public ObjectSchema objectSchema() {
        return new CustomJsonSchema();
      }

      static class CustomJsonSchema extends ObjectSchema {
        @JsonProperty private String label;

        @Override
        public void enrichWithBeanProperty(BeanProperty beanProperty) {
          super.enrichWithBeanProperty(beanProperty);
          JsonPropertyLabel labelAnnotation = beanProperty.getAnnotation(JsonPropertyLabel.class);
          if (labelAnnotation != null) {
            this.label = labelAnnotation.value();
          }
        }
      }
    }
  }
}

Однако при попытке этого кода в следующем классе:

public class POJOExample {
  @JsonPropertyDescription("first property description")
  @JsonPropertyLabel("first  property label")
  public PropertyExample first;

  @JsonPropertyDescription("second property description")
  @JsonPropertyLabel("second property label")
  public PropertyExample second;

  public static class PropertyExample {
    public String value;
  }
}

Схема JSON для этого выглядит так:

{
  "type" : "object",
  "id" : "urn:jsonschema:test:POJOExample",
  "properties" : {
    "first" : {
      "type" : "object",
      "id" : "urn:jsonschema:test:POJOExample:PropertyExample",
      "description" : "first property description",
      "properties" : {
        "value" : {
          "type" : "string"
        }
      },
      "label" : "first  property label"
    },
    "second" : {
      "type" : "object",
      "$ref" : "urn:jsonschema:test:POJOExample:PropertyExample",
      "description" : "second property description"
    }
  }
}

Как видите, у «first» есть как «description», так и «label», а у «second» только «description». Я предполагаю, что эта реализация связывает «метку» как функцию типа, доступную через ссылку. Как бы я сделал так, чтобы «метка» велась как «описание» и содержала бы его и во «втором» свойстве?

Кроме того, было бы оптимальным, если бы я мог сделать это динамически для случаев использования, когдаЯ не знаю значения строки ключевого слова заранее, но во время выполнения. Например, имея аннотацию JsonPropertyKeyword примерно так:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotation
public @interface JsonPropertyKeyword {
  String keyword();
  String value();
}

Таким образом, я мог бы аннотировать свойства с помощью @JsonPropertyKeyword(keyword="description", value="some description") и достичь того же эффекта, что и с @JsonPropertyDescription("some description"). Это возможно?

...