jsonschema-generator icon indicating copy to clipboard operation
jsonschema-generator copied to clipboard

Issue with record fields/methods

Open io7m opened this issue 8 months ago • 3 comments

Hello!

I was just trying out the schema generator and ran into a small problem with records.

For example:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@JsonSerialize
@JsonDeserialize
public record ProblemReport(
  String errorCode,
  @JsonProperty(value = "title", required = true)
  String message)
{
  @JsonProperty(value = "%type-schema", index = -1000, required = true)
  public String schemaSchema()
  {
    return "urn:com.io7m.cardant:inventory:1";
  }

  @JsonProperty(value = "%type", index = -999, required = true)
  public String schemaType()
  {
    return "Error";
  }

  @JsonProperty(value = "type", required = true)
  public String type()
  {
    return "urn:com.io7m.cardant/" + this.errorCode;
  }
}

Using the following generator calls:

    final var module =
      new JacksonModule(
        INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS
      );

    final var builder =
      new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12);

    builder.with(module);
    builder.forFields().withNullableCheck(_ -> false);
    builder.forMethods().withNullableCheck(_ -> false);

    final var config =
      builder.build();

    final var generator =
      new SchemaGenerator(config);

    var mapper =
      JsonMapper.builder()
        .enable(SerializationFeature.INDENT_OUTPUT)
        .build();

    System.out.println(
      mapper.writeValueAsString(
        generator.generateSchema(
          ProblemReport.class
        )
      )
    );

Yields the following schema:

{
  "type" : "object",
  "properties" : {
    "errorCode" : {
      "type" : "string"
    },
    "title" : {
      "type" : "string"
    },
    "%type()" : {
      "type" : "string"
    },
    "%type-schema()" : {
      "type" : "string"
    },
    "title()" : {
      "type" : "string"
    },
    "type()" : {
      "type" : "string"
    }
  }
}

As you can see, things are a little strange. The message field is annotated to result in a title property. However, due to the way records work, there'll also be a message() method in the bytecode which inherits the annotation from the message field, and so the generator dutifully generates a definition for a title() property.

I think it's also a bit strange that there'll be properties generated that include () in the names at all, but I'm only an hour into trying out the generator so maybe this is something I'm supposed to configure myself.

io7m avatar Mar 11 '25 21:03 io7m

This set of options makes things a little less surprising:

final var module =
      new JacksonModule(
        INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS,
        RESPECT_JSONPROPERTY_REQUIRED
      );

    final var builder =
      new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12);

    builder.with(module);
    builder.with(Option.DEFINITIONS_FOR_ALL_OBJECTS);
    builder.with(Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS);
    builder.with(Option.FLATTENED_ENUMS);
    builder.with(Option.FLATTENED_OPTIONALS);
    builder.without(Option.NONPUBLIC_STATIC_FIELDS);

    builder.forFields()
      .withNullableCheck(_ -> false);
    builder.forMethods()
      .withNullableCheck(_ -> false);

    builder.forMethods()
      .withPropertyNameOverrideResolver(target -> {
        return target.getAnnotation(JsonProperty.class).value();
      });

    final var config =
      builder.build();

    final var generator =
      new SchemaGenerator(config);

    var mapper =
      JsonMapper.builder()
        .enable(SerializationFeature.INDENT_OUTPUT)
        .build();

    System.out.println(
      mapper.writeValueAsString(generator.generateSchema(ProblemReport.class))
    );

Yields (with the addition of some JsonPropertyDescription annotations):

{
  "type" : "object",
  "properties" : {
    "%type" : {
      "type" : "string"
    },
    "%type-schema" : {
      "type" : "string"
    },
    "errorCode" : {
      "type" : "string",
      "description" : "The cardant error code associated with the error."
    },
    "title" : {
      "type" : "string",
      "description" : "The RFC7807 error message."
    },
    "type" : {
      "type" : "string",
      "description" : "The RFC7807 error code."
    }
  },
  "required" : [ "%type", "%type-schema", "errorCode", "title", "type" ],
  "description" : "An RFC7807-compatible error message."
}

io7m avatar Mar 12 '25 13:03 io7m

Hi @io7m,

Sorry for the late reply. As you already identified, the selected Option values are pretty important here. That's why there are some standard OptionPreset variants available that I recommend you use in the SchemaGeneratorConfigBuilder's constructor, e.g.

    final var builder =
        new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON);

Please refer to the documentation what Option values are contained in each standard OptionPreset: https://victools.github.io/jsonschema-generator/#generator-options

CarstenWickner avatar Mar 22 '25 22:03 CarstenWickner

Thanks!

io7m avatar Mar 26 '25 08:03 io7m