jsonapi-simple

Simple implementation of the JSON:API specification

Лицензия

Лицензия

Категории

Категории

JSON Данные
Группа

Группа

io.github.seregaslm
Идентификатор

Идентификатор

jsonapi-simple
Последняя версия

Последняя версия

1.2.0
Дата

Дата

Тип

Тип

jar
Описание

Описание

jsonapi-simple
Simple implementation of the JSON:API specification
Система контроля версий

Система контроля версий

https://github.com/seregaSLM/jsonapi-simple/tree/master

Скачать jsonapi-simple

Как подключить последнюю версию

<!-- https://jarcasting.com/artifacts/io.github.seregaslm/jsonapi-simple/ -->
<dependency>
    <groupId>io.github.seregaslm</groupId>
    <artifactId>jsonapi-simple</artifactId>
    <version>1.2.0</version>
</dependency>
// https://jarcasting.com/artifacts/io.github.seregaslm/jsonapi-simple/
implementation 'io.github.seregaslm:jsonapi-simple:1.2.0'
// https://jarcasting.com/artifacts/io.github.seregaslm/jsonapi-simple/
implementation ("io.github.seregaslm:jsonapi-simple:1.2.0")
'io.github.seregaslm:jsonapi-simple:jar:1.2.0'
<dependency org="io.github.seregaslm" name="jsonapi-simple" rev="1.2.0">
  <artifact name="jsonapi-simple" type="jar" />
</dependency>
@Grapes(
@Grab(group='io.github.seregaslm', module='jsonapi-simple', version='1.2.0')
)
libraryDependencies += "io.github.seregaslm" % "jsonapi-simple" % "1.2.0"
[io.github.seregaslm/jsonapi-simple "1.2.0"]

Зависимости

compile (6)

Идентификатор библиотеки Тип Версия
org.springframework : spring-web jar 5.3.1
org.projectlombok : lombok jar 1.18.16
com.fasterxml.jackson.core : jackson-databind jar 2.11.3
com.fasterxml.jackson.datatype : jackson-datatype-jsr310 jar 2.11.3
io.springfox : springfox-swagger2 jar 3.0.0
org.projectlombok : lombok-maven-plugin jar 1.18.16.0

test (3)

Идентификатор библиотеки Тип Версия
org.junit.jupiter : junit-jupiter jar 5.7.0
org.hamcrest : hamcrest jar 2.2
org.mockito : mockito-core jar 3.6.0

Модули Проекта

Данный проект не имеет модулей.

Overview

Simple implementation of the JSON:API specification (only required output fields). This library implements only top-level fields: data, links, errors and meta without any relationships, includes and others.

Often we only need standard of output all our endpoints especially when using many types of communications like HTTP query, websockets, queues etc and don't need complex entities inner relationships in our API but it's good if implementing some exists standards so this library for this goals.

Docs

See project page

Usage

Add dependency to your project:

<dependency>
    <groupId>io.github.seregaslm</groupId>
    <artifactId>jsonapi-simple</artifactId>
    <version>1.2.0</version>
</dependency>

Build Response

See documentation part: response structure

Each response DTO should contain the annotation @JsonApiType("resource-type") with any resource type identifier and the annotation @JsonApiId without arguments on field which will be unique identifier this item, usually this field is id.

Each response may contain generics (if you planning to use SWAGGER) or may not (without SWAGGER), for example both variants correct:

public class RestController {
    // The simplest option without SWAGGER support
    public Response responseWithoutGenerics() {
        return Response.builder()
           .build();
    }
  
    // This option will display correctly in SWAGGER with all DTO fields 
    public Response<SomeDto> responseWithGenerics() {
        return Response.<SomeDto, SomeDto>builder()
           .build();
    }
}

Parametrized response may be 2 types:

  • as list - first parameter in response builder must be only <List<Data<YourDto>>>:
    public class RestController {
        public Response<List<Data<SomeDto>>> responseAsList() {
            return Response.<List<Data<SomeDto>>, SomeDto>builder()
               .build();
        }
    }
  • as object:
    public class RestController {
        public Response<SomeDto> responseAsObject() {
            return Response.<SomeDto, SomeDto>builder()
               .build();
        }
    }
  • as empty response with Void class as parameter:
    public class RestController {
        public Response<Void> responseAsObject() {
            return Response.<Void, Void>builder()
               .build();
        }
    }

You can add URI prefix for self links, by default using prefix only from @JsonApiType annotation for example:

@RestController
@RequestMapping(value = "/api/v1/app", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class RestController {
    @GetMapping
    public Response<SomeDto> responseAsObject() {
        return Response.<SomeDto, SomeDto>builder()
            .uri("/api/v1/app")
            .build();
    }
}

This produces self links like (data fields are omitted):

{
  "data": { 
    "links": {
      "self": "/api/v1/app/test-object/7a543e90-2961-480e-b1c4-51249bf0c566"
    }
  },
  "meta": {}
}

We can also use spring placeholders in the uri the same with the @RequestMapping value. In this case we must add all placeholder values in the same order as in the uri. Supported placeholders format:

  • {some_id}
  • {someId}
  • ${some_id}
  • ${someId}
@RestController
@RequestMapping(value = "/api/v1/books/{book_id}/users/${userId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class RestController {
    @GetMapping
    public Response<SomeDto> responseAsObject() {
        final int bookId = 1024;
        final int userId = 2048;
 
        return Response.<SomeDto, SomeDto>builder()
            .uri("/api/v1/books/{book_id}/users/${userId}", bookId, userId)
            .build();
    }
}

This produces self links like (data fields are omitted):

{
  "data": { 
    "links": {
      "self": "/api/v1/books/1024/users/2048/test-object/7a543e90-2961-480e-b1c4-51249bf0c566"
    }
  },
  "meta": {}
}

Filtering

See documentation part: fetching-filtering

If you want to use request filters with annotation @RequestJsonApiFilter add argument resolver in your configuration for example:

import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new JsonApiFilterArgumentResolver());
    }
}

Then you can use annotation @RequestJsonApiFilter in controllers with one of the filtering operators:

  • in
  • not_in
  • eq
  • ne
  • gt
  • gte
  • lt
  • lte
  • contain
  • not_contain

If filter operator omitted eq will be used by default!

For example:

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping(value = "/app", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class RestController {
    @GetMapping
    public Response<Void> get(final @RequestJsonApiFilter Filter filter) throws Exception {
        if (filter.hasParam("key")) {
            final List<String> filterValues = filter.getParam("key");

            // do something with filter param
        }
        return Response.<Void, Void>builder()
           .build();
    }
}

Now if request will be contained fields like filter[key1][in]=value1,value2&filter[key2]=value1 we can get them in the Filter object.

Sparse fieldsets

See documentation part: fetching-sparse-fieldsets

If you want to use request sparse fieldsets with annotation @RequestJsonApiFieldSet add argument resolver in your configuration for example:

import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new JsonApiSpreadFieldSetArgumentResolver());
    }
}

Then you can use annotation @RequestJsonApiFieldSet in controllers.

For example:

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping(value = "/app", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class RestController {
    @GetMapping
    public Response<Void> get(final @RequestJsonApiFieldSet FieldSet fieldSet) throws Exception {
        if (fieldSet.hasResource("resource-type")) {
            final Set<String> resourceFields = fieldSet.getFieldsByResourceType("resource-type");

            // do something with resource fields
        }

        if (fieldSet.isEmpty()) {
            // do something with empty resource fields
            // there we can add for example any default fields
            fieldSet.addFields("resource-type", Set.of("default_field"));
        }

        if (!fieldSet.containsFields(Set.of("required_field_1", "required_field_2"))) {
            // do something when required fields in resource fields
            // are absent
        }
        return Response.<Void, Void>builder()
           .build();
    }
}

Now if request will be contained fields like fields[resource-type-1]=field_1,field_2&fields[resource-type-2]=field_3 we can get them in the FieldSet object.

Other response examples

Example response with one data object:

// Pseudo code
@JsonApiType("test-object")
public static class TestDto {
    @JsonApiId
    private UUID id;
    private String name;
    private LocalDateTime createDate;
}

public class io.github.seregaslm.jsonapi.simple.Test {
    public Response<TestDto> main() {
        return Response.<TestDto, TestDto>builder()
            .data(
                TestDto.builder()
                    .id(UUID.randomUUID())
                    .name("io.github.seregaslm.jsonapi.simple.Test string")
                    .createDate(LocalDateTime.now(ZoneOffset.UTC))
                    .build()
            ).build();
    }
}
{
  "data": {
    "type":"test-object",
    "id":"7a543e90-2961-480e-b1c4-51249bf0c566",
    "attributes": {
      "name":"io.github.seregaslm.jsonapi.simple.Test string",
      "createDate":"2019-10-08T18:46:53.40297"
    }, 
    "links": {
      "self": "/test-object/7a543e90-2961-480e-b1c4-51249bf0c566"
    }
  },
  "meta": {
    "api": {
      "version":"1"
    },
    "page": {
      "maxSize":25,
      "total":1
    }
  }
}

Example response with list of data objects:

// Pseudo code
@JsonApiType("test-object")
public static class TestDto {
    @JsonApiId
    private UUID id;
    private String name;
    private LocalDateTime createDate;
}

public class io.github.seregaslm.jsonapi.simple.Test {
    public Response<List<Data<TestDto>>> main() {
        return Response.<List<Data<TestDto>>, TestDto>builder()
            .data(
                Arrays.asList(
                    TestDto.builder()
                        .id(UUID.randomUUID())
                        .name("io.github.seregaslm.jsonapi.simple.Test string 1")
                        .createDate(LocalDateTime.now(ZoneOffset.UTC))
                        .build(),
                    TestDto.builder()
                        .id(UUID.randomUUID())
                        .name("io.github.seregaslm.jsonapi.simple.Test string 2")
                        .createDate(LocalDateTime.now(ZoneOffset.UTC))
                        .build()
                )
            ).build();
    }
}
{
  "data": [ 
    {
      "type":"test-object",
      "id":"7a543e90-2961-480e-b1c4-51249bf0c566",
      "attributes": {
        "name":"io.github.seregaslm.jsonapi.simple.Test string 1",
        "createDate":"2019-10-08T18:46:53"
      }, 
      "links": {
        "self": "/test-object/7a543e90-2961-480e-b1c4-51249bf0c566"
      }
    },
    {
      "type":"test-object",
      "attributes": {
        "id":"b4070518-e9fc-11e9-81b4-2a2ae2dbcce4",
        "name":"io.github.seregaslm.jsonapi.simple.Test string 2",
        "createDate":"2019-10-08T18:46:51"
      }, 
      "links": {
        "self": "/test-object/b4070518-e9fc-11e9-81b4-2a2ae2dbcce4"
      }
    }
  ],
  "meta": {
    "api": {
      "version":"1"
    },
    "page": {
      "maxSize":25,
      "total":2
    }
  }
}

Example response with error (data is empty so we use Void class as generic parameter):

// Pseudo code
public class io.github.seregaslm.jsonapi.simple.Test {
    public Response<Void> main() {
        return Response.<Void, Void>builder()
            .error(
                HttpStatus.BAD_REQUEST,
                "TOKEN_ERROR",
                "Some errors occurred!"
            ).build();
    }
}
{
  "errors": [
    {
      "status": 400,
      "code": "TOKEN_ERROR",
      "detail": "Some errors occurred!"
    }
  ],
  "meta": {
    "api": {
      "version": "1"
    },
    "page": {
      "maxSize": 25,
      "total": 0
    }
  }
}

Example response with validation error and field name with invalid data (data is empty so we use Void class as generic parameter):

// Pseudo code
public class io.github.seregaslm.jsonapi.simple.Test {
    public Response<Void> main() {
        return Response.<Void, Void>builder()
            .validationError("field-name", "Field must be greater 0!")
            .build();
    }
}
{
  "errors": [
    {
      "status": 400,
      "code": "VALIDATION_ERROR",
      "detail": "Field must be greater 0!",
      "source": {
        "parameter": "field-name"
      }
    }
  ],
  "meta": {
    "api": {
      "version": "1"
    },
    "page": {
      "maxSize": 25,
      "total": 0
    }
  }
}

Версии библиотеки

Версия
1.2.0
1.1.0
1.0.0