cbor-java icon indicating copy to clipboard operation
cbor-java copied to clipboard

Streaming encoding

Open letmaik opened this issue 9 years ago • 6 comments

It would be nice if streaming encoding similar to Jackson is added. Important for me would be that I could stream the elements of a ByteString as well, since these form the major part of my payload.

letmaik avatar Jan 11 '16 12:01 letmaik

@neothemachine Basically, streaming encoding and decoding is already implemented. Could you please provide a small pseudo-code example what you want to achieve? Maybe I can help you to find the right direction; if not, I am sure we can find and implement a solution for your problem.

c-rack avatar Jan 11 '16 12:01 c-rack

The README only has a "Streaming Decoding Example" and I checked the code of CborEncoder to see if it has some kind of streaming interface but couldn't find anything.

new CborEncoder(outputstream).stream()
    .add("text")                // add string
    .add(1234)                  // add integer
    .add(new byte[] { 0x10 })   // add byte array
    .addArray()                 // add array
        .add(1)
        .add("text")
        .end()
    .close();

Something like that.

letmaik avatar Jan 11 '16 12:01 letmaik

OK, it is already there but not with a nice DSL as described. You could transform the example above to (free coded, not checked with IDE):

CborEncoder encoder = new CborEncoder(outputstream);
encoder.encode(new UnicodeString("text"));
encoder.encode(new UnsignedInteger(1234));
encoder.encode(new ByteString(new byte[] { 0x10 }));
encoder.encode(new Array().setChunked()); // start array
encoder.encode(new UnsignedInteger(1));
encoder.encode(new UnicodeString("text"));
encoder.encode(Special.BREAK); // close array
outputstream.close();

A more developer-friendly DSL would be much better, of course.

c-rack avatar Jan 11 '16 12:01 c-rack

I see. It seems natural to me to want to use the CborBuilder as such a DSL. So CborBuilder could be made into an interface where one implementation is the current builder class and the other directly writes to a CborEncoder instance (where the relations could be set up by my proposed .stream() call).

letmaik avatar Jan 11 '16 12:01 letmaik

Good idea! Allow me some time to implement this.

c-rack avatar Jan 11 '16 13:01 c-rack

In parallel I am implementing a generic streaming interface that I can use both with Jackson and cbor-java and which is supposed to be compatible to both, meaning which I can easily write adapters for. I think some things in CborBuilder are actually too much for a streaming interface, so maybe this has to be split in two interfaces or so.

For reference, this is what I currently got (targeted to my use case):

/**
 * A streaming encoder interface for JSON-compatible object structures
 * with additional hints for more advanced formats like CBOR encoders.
 * 
 * This interface is a mix of cbor-java's CborBuilder and Jackson's Streaming API,
 * with the goal of being compatible to both and being able to write
 * simple adapters for them.
 *  
 * @author Maik Riechert
 */
public interface StreamingEncoder {
    class ArrayHints {
        private final Long size;
        private final Class<Number> type;
        /**
         * 
         * @param size can be null
         * @param type can be null
         */
        public ArrayHints(Long size, Class<Number> type) {
            this.size = size;
            this.type = type;
        }
        boolean hasSize() {
            return size != null;
        }
        long getSize() {
            return size;
        }
        boolean hasType() {
            return type != null;
        }
        Class<Number> getType() {
            return type;
        }
    }

    interface ArrayEncoder <T> {
        ArrayEncoder<T> add(String value) throws IOException;
        ArrayEncoder<T> add(boolean value) throws IOException;
        ArrayEncoder<T> add(int value) throws IOException;
        ArrayEncoder<T> add(long value) throws IOException;
        ArrayEncoder<T> add(float value) throws IOException;
        ArrayEncoder<T> add(double value) throws IOException;
        ArrayEncoder<ArrayEncoder<T>> startArray() throws IOException;
        ArrayEncoder<ArrayEncoder<T>> startArray(ArrayHints hints) throws IOException;
        MapEncoder<ArrayEncoder<T>> startMap() throws IOException;
        T end() throws IOException;
    }

    interface MapEncoder <T> {
        MapEncoder<T> put(String key, String value) throws IOException;
        MapEncoder<T> put(String key, boolean value) throws IOException;
        MapEncoder<T> put(String key, int value) throws IOException;
        MapEncoder<T> put(String key, long value) throws IOException;
        MapEncoder<T> put(String key, float value) throws IOException;
        MapEncoder<T> put(String key, double value) throws IOException;
        ArrayEncoder<MapEncoder<T>> startArray(String key) throws IOException;
        ArrayEncoder<MapEncoder<T>> startArray(String key, ArrayHints hints) throws IOException;
        MapEncoder<MapEncoder<T>> startMap(String key) throws IOException;
        T end() throws IOException;
    }

    MapEncoder<StreamingEncoder> startMap() throws IOException;

    void end() throws IOException;

    // at the root level we only support Maps for now.  
}

letmaik avatar Jan 11 '16 13:01 letmaik