springfox icon indicating copy to clipboard operation
springfox copied to clipboard

Annotate POJOs that always serialize as primitives

Open BitFracture opened this issue 6 years ago • 9 comments

I am using v2.9.2 and have been unable to identify a way to mark a POJO as a primitive. I end up with a swagger definition that sees the value as an object, and lists out the properties within it which my API consumers should not know about. I've specified Jackson to always consume or produce primitive JSON from these POJOs, such as a string or number, but what I am missing is a corresponding Swagger annotation.

I saw @ApiModelProperty(dataType = "java.lang.String") etc. to do this one-off for a property within an object, but I need this class to be a primitive regardless of whether it is nested, and without needing to annotate every usage of it.

If a solution doesn't already exist, I am happy to propose some ideas and possibly contribute if the path for doing that is fairly clear.


The main reason I have to do this is HL7 FHIR, which specifies highly constrained primitive types in JSON. Some examples include strings representing identifiers. They can't have spaces or be longer than 64 characters, have to match a regex, and sometimes more. Similar constraints exist for codes, urls, markdown blobs, etc. and some types break down into multiple parts that the Java API consumes separately. We represent these using POJOs so we can control the contained data and expose it in a sane manner.

BitFracture avatar Nov 30 '19 18:11 BitFracture

Here are two example classes: the model, and the abstracted primitive class.

@Data //lombok
class MyClass {
  private String simplePrimitive;
  private PrimitiveWrapper abstractedPrimitive;
}

@Data
class PrimitiveWrapper {
  private String type;
  private int version;

  @JsonValue
  @Override
  public String toString() {
    return String.format("%s|%d", type, version);
  }
  
  @JsonCreator
  public static PrimitiveWrapper fromString(String stringValue) {
    validateFormat(stringValue); //Abstracted for simplicity
    String[] parts = stringValue.split("|");
    return new PrimitiveWrapper(parts[0], Integer.parseInt(parts[1]));
  }
}

When I do this, Swagger UI is showing an example JSON for MyClass that looks like:

{
  "simplePrimitive": "string",
  "abstractedPrimitive": {
    "type": "string",
    "version": 0
  }
}

What it should look like is:

{
  "simplePrimitive": "string",
  "abstractedPrimitive": "string"
}

Or even better, the example could show the right pattern instead of just something generic like "string," but one problem at a time here :)

erikgreif-acc avatar Dec 02 '19 21:12 erikgreif-acc

Workarounds found: Tweak the Docket of every service that uses the model (real deep sigh here):

new Docket(DocumentationType.SWAGGER_2)
                .directModelSubstitute(PrimitiveWrapper.class, String.class)
                ...

Or, annotate every single instance of the wrapper within other models.

@Data //lombok
class MyClass {
  private String simplePrimitive;
  @ApiModelProperty(dataType = "java.lang.String"); //Workaround here
  private PrimitiveWrapper abstractedPrimitive;
}

To reiterate though, I am looking for making every usage of the wrapper appear as a string without reconfiguring Docket (outside control of my library) and without annotating every usage (a maintenance problem).

erikgreif-acc avatar Dec 03 '19 02:12 erikgreif-acc

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 24 '20 04:06 stale[bot]

Auto-closing ignored issues seems like a questionable behavior. Even if the desire is for community to fix this, I at least need someone else to acknowledge that it's a problem. The core issue here is that Jackson models are being interpreted as types that are not the same serial format they represent

BitFracture avatar Jul 03 '20 19:07 BitFracture

@BitFracture with over 700 issues, it is impossible for one person to triage and keep up. I have to be judicious about how I spend my time. There is always the option to reopen by just commenting your interest, so thank you for doing that. Also thank you for elaborating your exact need.

About this question. It is possible to override this behavior for parameters using @ApiImplicitParam(s), however it is does not extend to nested objects. @JsonValue is not directly supported, but for nested objects you could use alternate type rules as well, the caveat to doing that is it applies across the board. There is a feature request to allow it to do so selectively.

Also OpenAPI spec which I've been working on to try and release this following week has better support for this we can we can leverage.

dilipkrish avatar Jul 04 '20 00:07 dilipkrish

Thanks for the response. Yeah, the use case I have, at least, is completely concerning nested fields. I am using some type overrides at the Docket level as I commented above with my other account. It does work, but requires each service to make the same customization. Possibly unrelated, but I notice the generated JSON examples are not customizable in this case as well. So I can't show a good example of an object that serializes with date or other formatting. Rather I'm limited to an example of "string." So these are things to think about. I appreciate you marking it as a feature request

BitFracture avatar Jul 08 '20 01:07 BitFracture

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Oct 06 '20 02:10 stale[bot]

Bumping as still relevant

BitFracture avatar Oct 06 '20 02:10 BitFracture

Relevant for me, too.

gv2011 avatar Apr 21 '21 19:04 gv2011