DEV Community

kgoedert
kgoedert

Posted on

Jackson Readonly properties and swagger UI

Sometimes when you are building a REST API, you don't want the user to be able to set some specific property. If he decides to send it anyway, you want to discard it. Or, not even receive it. Jackson can help with that.

But it would be even nicer if the developer that is using your API, has site where he can test your API, and besides that have some documentation showing what is required and what is not. Here, swagger ui. But say the developer using your api doesn't like GUIs much, and prefers the command line. There is a solution for that too.

To show all this tools working together I am going to use quarkus and build upon on one of their examples.

The full code is available on github.

The code

The example has enpoint where the user can post some fruits, and the class that represent the fruits.

@Path("/fruits")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class FruitResource {

    @POST
    @Operation(summary = "POSTs a new fruit to the list")
    @APIResponse(responseCode = "200", description = "Fruit registration successful")
    @APIResponse(responseCode = "500", description = "Server unavailable")
    public Response save(Fruit fruit) {
        if (fruit.getCreated() == null) {
            fruit.addCreationDate();
        }

        if (fruit.getUuid() == null) {
            fruit.addUUID();
        }
        return Response.ok(fruit).build();
    }
}
@Schema(name = "Fruit", description = "Represents a fruit")
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class Fruit {
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    @Schema(required = false, readOnly = true)
    private String uuid;

    @Schema(required = true, example = "The name of the fruit you want to save")
    private String name;

    @Schema(required = false, example = "Some description for the fruit")
    private String description;

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    @JsonFormat(shape = Shape.STRING, pattern = "MM/dd/yyyy HH:mm:ss")
    @Schema(required = false, readOnly = true, pattern = "MM/dd/yyyy HH:mm:ss")
    private LocalDateTime created;

    public void addCreationDate() {
        this.created = LocalDateTime.now();
    }

    public void addUUID() {
        this.uuid = UUID.randomUUID().toString();
    }

    public LocalDateTime getCreated() {
        return created;
    }

    public String getUuid() {
        return uuid;
    }
}

Here, you can see the use of swagger open api annotations, @Operation and @APIResponse. Their purpose is to document the endpoint. It will be shown in swagger-ui and when you access the open api URL of the application.

In the fruit class, the @Schema annotations serve the purpose of documenting and marking if the property is required, its pattern, as in the case of the date field, and setting it as readonly. This annotation is used by swagger and open api. It means that when the user receives the result of the post, the result will have this field present, but when trying to post a fruit, this field will be ignored.

The @JsonAutoDetect, @JsonProperty and @JsonFormat do the same for jackson, that is responsible for parsing the results. If the property is marking as readonly, it will be ignored when something is posted, but it will be available on the response.

There is also the writeonly version of the property, that you can use for example to send something when you post, like a password, the you do not want it shown, when the user makes a get request.

You can see the swagger UI on http://localhost:8080/swagger-ui and it will show you something like this

Alt Text

And in the open api url you should see something like this

curl http://localhost:8080/openapi
---
openapi: 3.0.1
info:
  title: Generated API
  version: "1.0"
paths:
  /fruits:
    post:
      summary: POSTs a new fruit to the list
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Fruit'
      responses:
        "500":
          description: Server unavailable
        "200":
          description: Fruit registration successful
components:
  schemas:
    Fruit:
      description: Represents a fruit
      required:
      - name
      type: object
      properties:
        created:
          allOf:
          - $ref: '#/components/schemas/LocalDateTime'
          - pattern: MM/dd/yyyy HH:mm:ss
            readOnly: true
        description:
          type: string
          example: Some description for the fruit
        name:
          type: string
          example: The name of the fruit you want to save
        uuid:
          type: string
          readOnly: true
    LocalDateTime:
      format: date-time
      type: string


`

This makes it very easy to document your APIs.

Top comments (0)