简体   繁体   中英

How to read json sequence from file incrementally in Jackson?

One process is creating a log that is a sequence of json documents. Another process wants to read this log as it is being generated (similar to tail -f) and parse it using Jackson JsonParser (and ObjectReader). How do you ask JsonParser or MappingIterator not to close itself when Reader.read returns -1 temporarily?

Example that attempts to create JsonReader.readValues over a Reader that EOFs often:

import static org.junit.Assert.*;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;

public class ParitalParserTest {
    /** Reader that temporarily returns -1 (EOF) to simulate growing file */
    private static class StringReaderWithEofs extends Reader {
        public final String string;
        private int pos;
        private int limit;
        public StringReaderWithEofs(String string) {
            this.string = string;
        }
        public void limit(int limit) { 
            this.limit = limit;
        }
        @Override public int read(char[] cbuf, int off, int len) throws IOException {
            int newPos = Math.min(pos + len, limit);
            string.getChars(pos, newPos, cbuf, off);
            int r = newPos - this.pos;
            if (r == 0) r = -1;
            this.pos = newPos;
            return r;
        }
        @Override public void close() throws IOException {}
    }
    /** POJO log event */
    public static class LogEvent {
        private final String message;
        @JsonCreator
        public LogEvent(@JsonProperty("message") String message) {
            this.message = message;
        }
        public String getMessage() {
            return message;
        }
        @Override public int hashCode() {
            return Objects.hash(message);
        }
        @Override public boolean equals(Object obj) {
            if (! (obj instanceof LogEvent) || obj.getClass() != this.getClass()) return false;
            LogEvent o = (LogEvent) obj;
            return Objects.equals(message, o.message);
        }
        @Override public String toString() {
            return String.format("%s{message=%s}", getClass().getSimpleName(), message);
        }

    }
    public void test(boolean withEofs) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        ObjectReader jsonReader = mapper.reader(LogEvent.class);
        String one = "{\"message\":\"one\"}\n";
        String two = "{\"message\":\"two\"}\n";
        String three = "{\"message\":\"three\"}\n";
        StringReaderWithEofs reader = new StringReaderWithEofs(one + two + three);
        MappingIterator<LogEvent> it = null;
        List<LogEvent> values = new ArrayList<>();
        int firstLimit = withEofs ? 1 : reader.string.length();
        int lastLimit = reader.string.length();
        for (int i = firstLimit; i <= lastLimit; i++) {
            reader.limit(i);
            if (it == null) {
                it = jsonReader.readValues(reader);
            }
            while (it.hasNext()) {
                LogEvent value = it.next();
                values.add(value);
            }
        }
        List<LogEvent> expected = Arrays.asList(new LogEvent("one"), new LogEvent("two"), new LogEvent("three"));
        assertEquals(expected, values);
    }
    @Test public void testReadFullFile() throws IOException {
        test(false);
    }
    @Test public void testReadPartialFile() throws IOException {
        test(true);
    }

}

This is bit tricky, but I would probably try to create a Reader that actually blocks if no data is available, sleeps a bit, checks, and so on. This way Jackson need not concern itself with lack of content.

Return 0 from Reader or InputStream will not actually help, as there isn't much parser can do, as there is really no way to properly block. Or, put another way: underlying reader or stream MUST try to read at least one byte (if one or more requested), blocking if none is found; or return -1 if nothing will be available.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM