private static HanabiraPost createAndSavePost(
      JsonObject postObject, int threadId, String boardKey) {
    // todo files
    int postId = postObject.get("post_id").getAsInt();
    LocalDateTime modifiedDate = extractLocatDateTime(postObject.get("last_modified"));

    HanabiraPost cachedPost = Hanabira.getStem().findPostById(postId);
    if (cachedPost != null) {
      if (modifiedDate == null || !cachedPost.isUpToDate(modifiedDate)) {
        // update
        cachedPost.setModifiedDate(modifiedDate);
        cachedPost.setMessage(postObject.get("message").getAsString());
        cachedPost.setName(postObject.get("name").getAsString());
        cachedPost.setSubject(postObject.get("subject").getAsString());
      }
    } else {
      // brand new post
      int displayId = postObject.get("display_id").getAsInt();
      LocalDateTime createdDate = extractLocatDateTime(postObject.get("date"));
      String message = postObject.get("message").getAsString();
      String subject = postObject.get("subject").getAsString();
      String name = postObject.get("name").getAsString();
      boolean op = postObject.get("op").getAsBoolean();
      int boardId =
          boardKey != null
              ? HanabiraBoard.Info.getIdForKey(boardKey)
              : postObject.get("board_id").getAsInt();
      cachedPost =
          new HanabiraPost(
              displayId,
              modifiedDate,
              createdDate,
              postId,
              message,
              subject,
              boardId,
              name,
              threadId,
              op);
      Hanabira.getStem().savePost(cachedPost, boardKey);
    }
    return cachedPost;
  }
  private static HanabiraThread createThreadWithPosts(
      JsonObject threadObject, @Nullable String boardKey) {
    int threadId = threadObject.get("thread_id").getAsInt();
    LocalDateTime modifiedDate = extractLocatDateTime(threadObject.get("last_modified"));

    HanabiraThread thread = Hanabira.getStem().findThreadById(threadId);
    boolean oldThread = thread != null;
    if (oldThread) {
      if (modifiedDate == null || !thread.isUpToDate(modifiedDate)) {
        // update thread meta
        thread.setPostsCount(threadObject.get("posts_count").getAsInt());
        thread.setArchived(threadObject.get("archived").getAsBoolean());
        thread.setFilesCount(threadObject.get("files_count").getAsInt());
        thread.setTitle(threadObject.get("title").getAsString());
        thread.setLastHit(extractLocatDateTime(threadObject.get("last_hit")));
        thread.setModifiedDate(modifiedDate);
      }
    } else {
      // brand new thread
      int displayId = threadObject.get("display_id").getAsInt();
      boolean archived = threadObject.get("archived").getAsBoolean();
      int postsCount = threadObject.get("posts_count").getAsInt();
      int filesCount = threadObject.get("files_count").getAsInt();
      String title = threadObject.get("title").getAsString();
      int boardId =
          boardKey != null
              ? HanabiraBoard.Info.getIdForKey(boardKey)
              : threadObject.get("board_id").getAsInt();
      boolean autosage = threadObject.get("autosage").getAsBoolean();
      LocalDateTime lastHit = extractLocatDateTime(threadObject.get("last_hit"));

      thread =
          new HanabiraThread(
              displayId,
              threadId,
              modifiedDate,
              postsCount,
              filesCount,
              boardId,
              archived,
              title,
              autosage,
              lastHit);
      Hanabira.getStem().saveThread(thread, boardKey);
    }

    // cache posts
    for (JsonElement postElement : threadObject.getAsJsonArray("posts")) {
      createAndSavePost(postElement.getAsJsonObject(), threadId, boardKey);
    }

    if (!oldThread) {
      // set thread creation date
      HanabiraPost opPost = Hanabira.getStem().findPostByDisplayId(boardKey, thread.getDisplayId());
      if (opPost == null || !opPost.isOp()) {
        throw new InputMismatchException("Op post not received");
      }
      thread.setCreatedDate(opPost.getCreatedDate());
    }
    return thread;
  }