Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

JSON на входе и выходе

REST API обменивается данными в формате JSON. Javalin не превращает объекты в JSON сам, а поручает это библиотеке Jackson, которую мы подключили в pom.xml. Ваша задача — описать данные классами Java и вызвать ctx.json(...) для ответа или ctx.bodyAsClass(...) для чтения запроса.

Модель данных

Пост блога опишем записью (record). Создайте два файла рядом с Main.java:

// src/main/java/com/example/blog/Post.java
package com.example.blog;
 
public record Post(long id, String title, String body, String author) {
}
// src/main/java/com/example/blog/NewPost.java
package com.example.blog;
 
public record NewPost(String title, String body, String author) {
}

Post — пост целиком, с номером. NewPost — то, что присылает клиент при создании: номер назначает сервер.

Хранилище в памяти

Пока базы данных нет, посты будут жить в словаре. Базу подключим в главе 4.

// src/main/java/com/example/blog/PostStore.java
package com.example.blog;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
 
public class PostStore {
    private final Map<Long, Post> posts = new ConcurrentHashMap<>();
    private final AtomicLong nextId = new AtomicLong(1);
 
    public PostStore() {
        create(new NewPost("Привет, Javalin", "Первый пост.", "Анна"));
        create(new NewPost("Маршруты", "Как работают маршруты.", "Олег"));
    }
 
    public List<Post> findAll() {
        return new ArrayList<>(posts.values());
    }
 
    public Optional<Post> findById(long id) {
        return Optional.ofNullable(posts.get(id));
    }
 
    public Post create(NewPost data) {
        long id = nextId.getAndIncrement();
        Post post = new Post(id, data.title(), data.body(), data.author());
        posts.put(id, post);
        return post;
    }
 
    public boolean delete(long id) {
        return posts.remove(id) != null;
    }
}

ConcurrentHashMap и AtomicLong нужны, потому что Jetty обрабатывает запросы в нескольких потоках одновременно.

Ответ в JSON

Создайте хранилище в main и замените маршруты /posts:

// src/main/java/com/example/blog/Main.java
PostStore store = new PostStore();
 
Javalin app = Javalin.create(config -> {
    config.routes.get("/posts", ctx -> ctx.json(store.findAll()));
    config.routes.get("/posts/{id}", ctx -> {
        long id = ctx.pathParamAsClass("id", Long.class).get();
        Post post = store.findById(id)
                .orElseThrow(() -> new NotFoundResponse("Пост " + id + " не найден"));
        ctx.json(post);
    });
});

ctx.json превращает объект в JSON и ставит заголовок Content-Type: application/json. NotFoundResponse из пакета io.javalin.http отвечает кодом 404 — подробнее в следующем уроке.

curl http://localhost:8080/posts/1
{"id":1,"title":"Привет, Javalin","body":"Первый пост.","author":"Анна"}

JSON в запросе

ctx.bodyAsClass читает тело запроса и создаёт из него объект:

// src/main/java/com/example/blog/Main.java
config.routes.post("/posts", ctx -> {
    NewPost data = ctx.bodyAsClass(NewPost.class);
    ctx.status(201).json(store.create(data));
});
curl -i -X POST http://localhost:8080/posts \
     -H "Content-Type: application/json" \
     -d '{"title":"JSON в Javalin","body":"Всю работу делает Jackson.","author":"Анна"}'
HTTP/1.1 201 Created
Content-Type: application/json

{"id":3,"title":"JSON в Javalin","body":"Всю работу делает Jackson.","author":"Анна"}

Частая ошибка

По умолчанию Jackson отказывается читать JSON с полем, которого нет в классе. Запрос с лишним полем "tags" получает 500 Server Error, а в логе:

WARN io.javalin.Javalin - Uncaught exception
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "tags"
(class com.example.blog.NewPost), not marked as ignorable (3 known properties: "title", "body", "author")

Если лишние поля нужно молча пропускать, добавьте к записи аннотацию @JsonIgnoreProperties(ignoreUnknown = true) из пакета com.fasterxml.jackson.annotation.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.