Commit 8e128f5f authored by Morten Kjetland's avatar Morten Kjetland
Browse files

Fixes #17 - Added support for @JsonSchemaDefault-annotation which injects...

Fixes #17 - Added support for @JsonSchemaDefault-annotation which injects default-values into the schema
parent 9e3305a6
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -11,7 +11,11 @@ Current version: *1.0.9*

* JSON Schema Draft v4
* Supports polymorphism (**@JsonTypeInfo**, **MixIn**, and **registerSubtypes()**) using JsonSchema's **oneOf**-feature.
* Supports schema customization using **@JsonSchemaDescription**, **@JsonSchemaFormat** and **@JsonSchemaTitle**
* Supports schema customization using:
  - **@JsonSchemaDescription**
  - **@JsonSchemaFormat**
  - **@JsonSchemaTitle**
  - **@JsonSchemaDefault**
* Supports many Javax-validation @Annotations
* Works well with Generated GUI's using [https://github.com/jdorn/json-editor](https://github.com/jdorn/json-editor)
  - (Must be configured to use this mode)
+13 −0
Original line number Diff line number Diff line
package com.kjetland.jackson.jsonSchema.annotations;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ METHOD, FIELD, PARAMETER, TYPE })
@Retention(RUNTIME)
public @interface JsonSchemaDefault {
    String value();
}
+29 −2
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ import java.lang.reflect.{Field, Method, ParameterizedType}
import java.time.{LocalDate, LocalDateTime, LocalTime, OffsetDateTime}
import java.util
import java.util.Optional
import javax.validation.constraints.{Pattern, Max, Min, NotNull, Size}
import javax.validation.constraints.{Max, Min, NotNull, Pattern, Size}

import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
import com.fasterxml.jackson.core.JsonParser.NumberType
@@ -14,7 +14,7 @@ import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.introspect.{AnnotatedClass, JacksonAnnotationIntrospector}
import com.fasterxml.jackson.databind.node.{ArrayNode, JsonNodeFactory, ObjectNode}
import com.kjetland.jackson.jsonSchema.annotations.{JsonSchemaDescription, JsonSchemaFormat, JsonSchemaTitle}
import com.kjetland.jackson.jsonSchema.annotations.{JsonSchemaDefault, JsonSchemaDescription, JsonSchemaFormat, JsonSchemaTitle}
import org.slf4j.LoggerFactory

object JsonSchemaGenerator {
@@ -239,6 +239,12 @@ class JsonSchemaGenerator
              node.put("pattern", pattern.regexp())
          }

          // Look for @JsonSchemaDefault
          Option(p.getAnnotation(classOf[JsonSchemaDefault])).map {
            defaultValue =>
              node.put("default", defaultValue.value())
          }

          // Look for @Size
          Option(p.getAnnotation(classOf[Size]))
              .map {
@@ -322,6 +328,12 @@ class JsonSchemaGenerator
            max =>
              node.put("maximum", max.value())
          }

          // Look for @JsonSchemaDefault
          Option(p.getAnnotation(classOf[JsonSchemaDefault])).map {
            defaultValue =>
              node.put("default", defaultValue.value().toLong )
          }
      }

      new JsonNumberFormatVisitor  with EnumSupport {
@@ -361,6 +373,12 @@ class JsonSchemaGenerator
            max =>
              node.put("maximum", max.value())
          }

          // Look for @JsonSchemaDefault
          Option(p.getAnnotation(classOf[JsonSchemaDefault])).map {
            defaultValue =>
              node.put("default", defaultValue.value().toInt)
          }
      }


@@ -384,6 +402,15 @@ class JsonSchemaGenerator

      node.put("type", "boolean")

      currentProperty.map {
        p =>
          // Look for @JsonSchemaDefault
          Option(p.getAnnotation(classOf[JsonSchemaDefault])).map {
            defaultValue =>
              node.put("default", defaultValue.value().toBoolean)
          }
      }

      new JsonBooleanFormatVisitor with EnumSupport {
        val _node = node
        override def format(format: JsonValueFormat): Unit = {
+32 −3
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@ package com.kjetland.jackson.jsonSchema
import java.time.{LocalDate, LocalDateTime, OffsetDateTime}
import java.util
import java.util.{Optional, TimeZone}
import javax.validation.constraints.{Pattern, Max, Min, NotNull, Size}
import javax.validation.constraints.{Max, Min, NotNull, Pattern, Size}

import com.fasterxml.jackson.annotation.{JsonProperty, JsonSubTypes, JsonTypeInfo, JsonValue}
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
@@ -15,6 +15,7 @@ import com.fasterxml.jackson.datatype.joda.JodaModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.github.fge.jsonschema.main.JsonSchemaFactory
import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaDefault
import com.kjetland.jackson.jsonSchema.testData._
import com.kjetland.jackson.jsonSchema.testData.mixin.{MixinChild1, MixinModule, MixinParent}
import org.scalatest.{FunSuite, Matchers}
@@ -213,6 +214,15 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

  test("Generate schema for regular class which has a property of class annotated with @JsonTypeInfo") {

    def assertDefaultValues(schema:JsonNode): Unit ={
      assert( schema.at("/properties/stringWithDefault/type").asText() == "string" )
      assert( schema.at("/properties/stringWithDefault/default").asText() == "x" )
      assert( schema.at("/properties/intWithDefault/type").asText() == "integer" )
      assert( schema.at("/properties/intWithDefault/default").asInt() == 12 )
      assert( schema.at("/properties/booleanWithDefault/type").asText() == "boolean" )
      assert( schema.at("/properties/booleanWithDefault/default").asBoolean() == true )
    }

    // Java
    {
      val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoWithParent)
@@ -220,6 +230,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

      assert(false == schema.at("/additionalProperties").asBoolean())
      assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
      assertDefaultValues(schema)


      assertChild1(schema, "/properties/child/oneOf")
      assertChild2(schema, "/properties/child/oneOf")
@@ -233,6 +245,7 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

      assert(false == schema.at("/additionalProperties").asBoolean())
      assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
      assertDefaultValues(schema)

      assertChild1(schema, "/properties/child/oneOf", html5Checks = true)
      assertChild2(schema, "/properties/child/oneOf", html5Checks = true)
@@ -247,6 +260,7 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

      assert(false == schema.at("/additionalProperties").asBoolean())
      assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
      assertDefaultValues(schema)

      assertChild1(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.Child1", false)
      assertChild2(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.Child2", false)
@@ -260,6 +274,7 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

      assert(false == schema.at("/additionalProperties").asBoolean())
      assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
      assertDefaultValues(schema)

      assertChild1(schema, "/properties/child/oneOf", "Child1Scala")
      assertChild2(schema, "/properties/child/oneOf", "Child2Scala")
@@ -706,10 +721,13 @@ trait TestData {
    val p = new PojoWithParent
    p.pojoValue = true
    p.child = child1
    p.stringWithDefault = "y"
    p.intWithDefault = 13
    p.booleanWithDefault = true
    p
  }

  val pojoWithParentScala = PojoWithParentScala(true, child1Scala)
  val pojoWithParentScala = PojoWithParentScala(true, child1Scala, "y", 13, true)

  val classNotExtendingAnything = {
    val o = new ClassNotExtendingAnything
@@ -814,7 +832,18 @@ case class Child1Scala

case class Child2Scala(parentString:String, child2int:Int) extends ParentScala

case class PojoWithParentScala(pojoValue:Boolean, child:ParentScala)
case class PojoWithParentScala
(
  pojoValue:Boolean,
  child:ParentScala,

  @JsonSchemaDefault("x")
  stringWithDefault:String,
  @JsonSchemaDefault("12")
  intWithDefault:Int,
  @JsonSchemaDefault("true")
  booleanWithDefault:Boolean
)

case class ManyPrimitivesScala(_string:String, _integer:Int, _boolean:Boolean, _double:Double)

+18 −1
Original line number Diff line number Diff line
package com.kjetland.jackson.jsonSchema.testData;

import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaDefault;

public class PojoWithParent {

    public Boolean pojoValue;
    public Parent child;

    @JsonSchemaDefault("x")
    public String stringWithDefault;

    @JsonSchemaDefault("12")
    public int intWithDefault;

    @JsonSchemaDefault("true")
    public boolean booleanWithDefault;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
@@ -12,8 +23,11 @@ public class PojoWithParent {

        PojoWithParent that = (PojoWithParent) o;

        if (intWithDefault != that.intWithDefault) return false;
        if (booleanWithDefault != that.booleanWithDefault) return false;
        if (pojoValue != null ? !pojoValue.equals(that.pojoValue) : that.pojoValue != null) return false;
        return child != null ? child.equals(that.child) : that.child == null;
        if (child != null ? !child.equals(that.child) : that.child != null) return false;
        return stringWithDefault != null ? stringWithDefault.equals(that.stringWithDefault) : that.stringWithDefault == null;

    }

@@ -21,6 +35,9 @@ public class PojoWithParent {
    public int hashCode() {
        int result = pojoValue != null ? pojoValue.hashCode() : 0;
        result = 31 * result + (child != null ? child.hashCode() : 0);
        result = 31 * result + (stringWithDefault != null ? stringWithDefault.hashCode() : 0);
        result = 31 * result + intWithDefault;
        result = 31 * result + (booleanWithDefault ? 1 : 0);
        return result;
    }
}