@Wan-Derer
Зобанели на Хабре, волки́ ;((

Как из Flux собрать массив JSON?

Есть такой контроллер (Java, Reactor Netty):
public Publisher<Void> getFilterListByType(HttpServerRequest req, HttpServerResponse resp) {
    return resp
      .status(HttpResponseStatus.OK)
      .addHeader(CONTENT_TYPE, "application/json")
      .addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*")     // CORS
      .sendString(
        myService.getObjectsAsFlux()    // получить Flux<MyObj> 
          .map(item -> toJson(item))       // преобразовать в JSON
    );
}

который выдаёт строку вида
{"id": 123, "desc": "OLOLO"} {"id": 456, "desc": "AZAZA"}

т.е. для полноценного массива JSON не хватает квадратных скобок по краям и запятых между объектами.
Лог Netty показывает выдачу чанками (я правильно понимаю что объекты выдаются по мере их готовности и выдачи сервисом?).
Я могу добавить после .map код:
.reduce((a, b) -> a + "," + b)
.map(item -> "[" + item + "]")

и получит нормальный массив JSON. Но в этом случае я вместо Flux получаю Mono, а лог Netty показывает выдачу одним блоком. Т.е. я как бы теряю асинхронность. Или нет?

В общем, как такие вещи делать правильно?
(заглядывать в код Spring Web Flux или Vert.X я боюсь - я там ни в жисть не разберусь!)
  • Вопрос задан
  • 104 просмотра
Пригласить эксперта
Ответы на вопрос 3
mayton2019
@mayton2019 Куратор тега Java
Bigdata Engineer
При чем здесь Flux и Mono к JSON? Это разные уровни смыслов. А если-бы ты c XML работал? Ты явно указываешь getObjectsAsFlux зачит и будел флюкс. А если тебя смущает массив внутри респонса то оберни его объектом и будет тебе всегда объект.
Ответ написан
xez
@xez Куратор тега Java
TL Junior Roo
Вот мое демо:
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.time.Duration;
import java.util.stream.IntStream;

@Slf4j
@RequiredArgsConstructor
@RestController
public class WebFluxDemo {

    private final ObjectMapper objectMapper;

    @GetMapping("/getMyObjs")
    Flux<MyObj> getMyObjs() {
        return Flux.fromStream(IntStream.range(1, 6).boxed()
                        .map(String::valueOf)
                )
                .map(MyObj::new)
                .delayElements(Duration.ofSeconds(1))
                .log()
                ;
    }

    @GetMapping("/getMyObjsAsString")
    Flux<String> getMyObjsAsString() {
        return Flux.fromStream(IntStream.range(1, 6).boxed()
                        .map(String::valueOf))
                .map(MyObj::new)
                .map(this::mapToObj)
                .delayElements(Duration.ofSeconds(1))
                .log()
                ;
    }

    @SneakyThrows
    private String mapToObj(MyObj myObj) {
        return objectMapper.writeValueAsString(myObj);
    }

    private record MyObj(String id) {
    }
}


1. Валидный массив открывается и закрывается скобками. Т.е. он должен быть закрытым. Если он не закрыт - это не валидный массив.
2. Поэтому, если мы отдаем flux (см. getMyObjs()) то получим валидный массив, но ответ будет отдан только на onComplete. Т.е. все сразу и когда поступят все элементы.
3. Можно отдавать строку - тогда элементы будут отданы по мере поступления, но "валидного массива" никакого не будет.
Ответ написан
@Wan-Derer Автор вопроса
Зобанели на Хабре, волки́ ;((
Если кому интересно, придумал такой костыль:
Flux<String> items = ... здесь исходный Flux ... ;
Flux<Integer> numbers = Flux.generate(
  () -> 0,
  (state, sink) -> {
    sink.next(state);
    return state + 1;
  }
);

items.zipWith(numbers, (i,n) -> n + i)
  .map(item-> item.replace("0{", "{").replaceFirst("^[0-9]+", ", "))
  .startWith("[")
  .concatWithValues("]")
  .subscribe(System.out::println);


Т.е. мы сначала к каждой строке пририсовываем последовательный номер. Потом первый номер (0) просто затираем, а остальные меняем на запятую. Ну и потом оборачиваем всё квадратными скобками.
Не знаю насколько это хорошо, но работает :)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
Bell Integrator Хабаровск
До 400 000 ₽
Bell Integrator Ульяновск
До 400 000 ₽
Bell Integrator Ижевск
До 400 000 ₽