jackson-module-jsonSchema icon indicating copy to clipboard operation
jackson-module-jsonSchema copied to clipboard

Instant serialized to wrong type

Open kopax opened this issue 8 years ago • 9 comments

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"

kopax avatar Jan 21 '17 18:01 kopax

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.

cowtowncoder avatar Mar 24 '17 04:03 cowtowncoder

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 ?

kopax avatar Mar 24 '17 07:03 kopax

@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)

cowtowncoder avatar Mar 24 '17 23:03 cowtowncoder

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 .

kopax avatar Mar 25 '17 03:03 kopax

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.

cowtowncoder avatar Mar 25 '17 03:03 cowtowncoder

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 ?

kopax avatar Mar 25 '17 05:03 kopax

@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.

cowtowncoder avatar Mar 25 '17 22:03 cowtowncoder

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

kopax avatar Mar 26 '17 00:03 kopax

@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?

cowtowncoder avatar Mar 28 '17 23:03 cowtowncoder