FasterXML / jackson-module-jsonSchema

Module for generating JSON Schema (v3) definitions from POJOs
370 stars 136 forks source link

Instant serialized to wrong type #121

Open kopax opened 7 years ago

kopax commented 7 years ago

version

2.8

Description

I have serialized the following entity :

{
  "createdDate": {
    "nano": 326000000,
    "epochSecond": 1484986332
  },
  "lastModifiedDate": null,
  "createdById": null,
  "lastModifiedById": null,
  "active": true,
  "login": "admin",
  "roleList": [
    "ROLE_MANAGER",
    "ROLE_ADMIN"
  ],
  "username": "admin",
  "enabled": true,
  "authorities": [
    {
      "authority": "ROLE_MANAGER"
    },
    {
      "authority": "ROLE_ADMIN"
    }
  ],
  "accountNonLocked": true,
  "credentialsNonExpired": true,
  "accountNonExpired": true,
  "new": false
}

And it's schema json :

{
  "title": "Manager",
  "properties": {
    "new": {
      "title": "New",
      "readOnly": true,
      "type": "boolean"
    },
    "lastModifiedDate": {
      "title": "Last modified date",
      "readOnly": false,
      "type": "string",
      "format": "date-time",
      "$ref": "#/definitions/instant"
    },
    "credentialsNonExpired": {
      "title": "Credentials non expired",
      "readOnly": true,
      "type": "boolean"
    },
    "active": {
      "title": "Active",
      "readOnly": false,
      "type": "boolean"
    },
    "login": {
      "title": "Login",
      "readOnly": false,
      "type": "string"
    },
    "roleList": {
      "title": "Role list",
      "readOnly": false,
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "enabled": {
      "title": "Enabled",
      "readOnly": true,
      "type": "boolean"
    },
    "authorities": {
      "title": "Authorities",
      "readOnly": true,
      "type": "array",
      "items": {
        "type": "object"
      }
    },
    "createdDate": {
      "title": "Created date",
      "readOnly": false,
      "type": "string",
      "format": "date-time",
      "$ref": "#/definitions/instant"
    },
    "lastModifiedById": {
      "title": "Last modified by id",
      "readOnly": false,
      "type": "integer"
    },
    "accountNonExpired": {
      "title": "Account non expired",
      "readOnly": true,
      "type": "boolean"
    },
    "createdById": {
      "title": "Created by id",
      "readOnly": false,
      "type": "integer"
    },
    "username": {
      "title": "Username",
      "readOnly": true,
      "type": "string"
    },
    "accountNonLocked": {
      "title": "Account non locked",
      "readOnly": true,
      "type": "boolean"
    }
  },
  "definitions": {
    "instant": {
      "type": "string",
      "properties": {
        "nano": {
          "title": "Nano",
          "readOnly": true,
          "type": "integer"
        },
        "epochSecond": {
          "title": "Epoch second",
          "readOnly": true,
          "type": "integer"
        }
      }
    }
  },
  "type": "object",
  "$schema": "http://json-schema.org/draft-04/schema#"
}

I have test on http://www.jsonschemavalidator.net/ :

 Found 2 error(s)
Message:
Invalid type. Expected String but got Object.
Schema path:
#/definitions/instant/type
Message:
Invalid type. Expected String but got Object.
Schema path:
#/definitions/instant/type

It appear jackson try to serialize a java Instant but the generated schema contain the wrong type "object" instead of "string"

cowtowncoder commented 7 years ago

This is most likely since you have not registered module jackson-datatype-jsr310 (from https://github.com/FasterXML/jackson-modules-java8). Without it, Java 8 type Instant is not supoorted, as databind only requires Java 7 and can not (by default) add handles for Java 8 types.

kopax commented 7 years ago

This is most likely since you have not registered module jackson-datatype-jsr310 (from https://github.com/FasterXML/jackson-modules-java8). Without it, Java 8 type Instant is not supoorted, as databind only requires Java 7 and can not (by default) add handles for Java 8 types

I have jackson-datatype-jsr310 installed. My Java 8 type Instant are working well in my software.

Is there anything else I have to do in order for this to work ?

cowtowncoder commented 7 years ago

@kopax that should be all... what does the original Class (for which schema is generated) look like? Looking at generated schema it looks as if Instant type was considered a POJO, but InstantSerializerBase seems to have logic to produce proper information.

I assume this is reproducible with 2.8.7? (all components, specifically jackson-datatype-jsr310 important here)

kopax commented 7 years ago

My class look like this :

public abstract class VersionId extends LongId implements Cloneable {

    private static final Logger logger = LoggerFactory.getLogger(VersionId.class);

    @Version
    @NotNull
    @JdbcType(BIGINT)
    @Column(name = "VERSION")
    private Integer version;

    @CreatedDate
    @NotNull
    @JdbcType(TIMESTAMP)
    @Column(name = "CREATED_DATE")
    @JsonUnwrapped
    private Instant createdDate;

    @LastModifiedDate
    @NotNull
    @JdbcType(TIMESTAMP)
    @JsonUnwrapped
    @Column(name = "LAST_MODIFIED_DATE")
    private Instant lastModifiedDate;

    @CreatedBy
    @Column(name = "CREATED_BY")
    @NotNull
    @JdbcType(BIGINT)
    private Long createdById;

    @Column(name = "LAST_MODIFIED_BY")
    @LastModifiedBy
    @JdbcType(BIGINT)
    private Long lastModifiedById;

    @JdbcType(BOOLEAN)
    @Column(name = "ACTIVE")
    @NotNull
    private Boolean active = true;

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public Instant getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(Instant createdDate) {
        this.createdDate = createdDate;
    }

    public Instant getLastModifiedDate() {
        return lastModifiedDate;
    }

    public void setLastModifiedDate(Instant lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }

    public Long getCreatedById() {
        return createdById;
    }

    public void setCreatedById(Long createdById) {
        this.createdById = createdById;
    }

    public Long getLastModifiedById() {
        return lastModifiedById;
    }

    public void setLastModifiedById(Long lastModifiedById) {
        this.lastModifiedById = lastModifiedById;
    }

    public Boolean getActive() {
        return active;
    }

    public void setActive(Boolean active) {
        this.active = active;
    }
}

It is reproducible with 2.8.7. It sounds like the type string is wrong .

cowtowncoder commented 7 years ago

One thing to note is that @JsonUnwrapped has not effect here: Instants are not serialized as Beans by Jackson. But I don't think that affects schema generation.

kopax commented 7 years ago

Using @JsonUnwrapped allow me to have the date like this :

"createdDate": "2017-03-22T06:04:30.369Z",

Instead of something like

"createdDate": { "instant": "2017-03-22T06:04:30.369Z" }

But the json schema is still invalid. Why does it show string while it has some object metadata ?

cowtowncoder commented 7 years ago

@kopax That's not due to JsonUnwrapped; it should be just basic text format. Unwrapping only works for standard Beans, and if you do have jsr310 module, Instant should never be serialized as such. You can test that by removing annotations, should not make any difference.

But looking back the original problem, this is weird:

  "definitions": {
    "instant": {
      "type": "string",
      "properties": {
        "nano": {
          "title": "Nano",
          "readOnly": true,
          "type": "integer"
        },
        "epochSecond": {
          "title": "Epoch second",
          "readOnly": true,
          "type": "integer"
        }
      }
    }
  },

where type of string should not have properties. But I don't know how or why this would come about... I don't even know what definitions means here.

kopax commented 7 years ago

It's a jsonschema. I think it's jackson json that generate it. It's in a spring boot base application. (spring-data-rest)

cowtowncoder commented 7 years ago

@kopax Another strange thing there is this:

 "$schema": "http://json-schema.org/draft-04/schema#"

which is not something Jackson's schema module outputs: it only supports v3 of schema spec. (see JsonSchema.java).

I also can not see anything in schema module that would actually output definitions property.

Perhaps spring-data-rest (or core Spring package(s) it depends on) might be using a different JSON Schema generator?