generic protobuf deserializer in Java
Asked Answered
F

1

8

I'm trying to write a generic java class that can be used to deserialize/parse any protobuf message.

Here's how the code should look in a perfect world:

public abstract class ProtoDeserializer<T extends Message> {

    public T deserialize(final byte[] bytes) throws Exception {     
        Parser<T> parser = T.getParserForType(); // Syntax Error: this method is not static!

        T message = parser.parseFrom(bytes);
        validate(message);
        return message;
    }

    public abstract void validate(final T message) throws Exception;
}

However, I am not able to get the correct parser for a generic protobuf message. What is the correct way to implement such a generic class?

Fixation answered 23/5, 2017 at 12:22 Comment(10)
My current workaround is passing the parser as a constructor argument. But it would be nice to avoid it, because it's redundant information.Fixation
May I also ask if getParserForType is not static, how do you call it? Do you instantiate T?Germ
@JornVernee This is a syntax error in the above code, it doesn't actually work. It's just an example how I expected it to workFixation
I understand that. So the method is static? I have a suggestion but I'd need to know how you normally call getParserForType to know if it would work.Germ
@JornVernee The method is not static. This is working code (besides the NullptrException): T request; request.getParserForType()Fixation
Well, you don’t have an object instance, but want to invoke a non-static method. How is this supposed to work? It doesn’t work, even without Generics involved…Cosmonaut
@Cosmonaut Let me cite the sentence about the code: "Here's how the code should look in a perfect world". I'm perfectly aware that this does not work. That's why I even wrote Syntax Error in the comment and the reason why it does not work. This code snippet is just here to explain what I want to do. Not how it should be done (that's what I'm asking for). A piece of code tells more than a thousand words.Fixation
This doesn’t make any sense. Even “in a perfect world”, you can’t invoke a non-static method without a receiver instance. Otherwise, you could claim that “in a perfect world”, you could type eōzxvņrökwzℝiaklwo and the code would do whatever you want.Cosmonaut
@Cosmonaut Really? Are you serious? I'm not stupid, you know. In a perfect world, there would be a static method I could just call. But there isn't, due to some protobuf-internal reasons I guess. So i asked for an alternative solution. Maybe there's another way to get the parser in a generic way. But i guess you just prefer two paragraphs of text, instead of the pseudo-code I used to illustrate what I want to achieve.Fixation
You are still insisting that “in a perfect world”, code magically does what you want, even if there is no imaginable way, how this could work. How could a static getParserForType() method return the right Parser<T> every time, regardless of what T is? There is still no difference to claiming that “in a perfect world”, eōzxvņrökwzℝiaklwo would do whatever you want. Anyway, it’s not worth to continue this discussion.Cosmonaut
G
11

Simplest is to pass the parser as an argument to the constructor:

public abstract class ProtoDeserializer<T extends Message> {

    private final Parser<T> parser;

    public ProtoDeserializer(Parser<T> parser) {
        this.parser = parser;
    }

    public T deserialize(final byte[] bytes) throws Exception {    
        T message = parser.parseFrom(bytes);
        validate(message);
        return message;
    }

    public abstract void validate(final T message) throws Exception;
}

Passing the parser is my current workaround. But it would be nice to avoid it, because it's redundant information.

It may be redundant to you, but it is not redundant to the compiler/runtime.

If you consider that it is possible to create raw implementation of your class:

ProtoDeserializer proto = new ProtoDeserializer() {
    ...
};

The type T would have to come from somewhere.

This is just the reality of erased generics. If you need type information of a generic parameter, you will have to manually supply it.


Another hack you can try is to get the concrete type parameter from an implementing sub class:

private final Parser<T> parser; 

public ProtoDeserializer() {
    Class<?> subclass = this.getClass();

    try {
        ParameterizedType pType = (ParameterizedType) subclass.getGenericSuperclass();
        Class<T> tClass = (Class<T>) pType.getActualTypeArguments()[0];
        // In the case where the constructor for `T` takes no arguments.
        parser = tClass.newInstance().getParserForType();
    } catch(Throwable t) {
        throw new RuntimeException("Subclass not compatible", t);
    }
}

This would work as long as the subclass directly implements ProtoDeserializer with a concrete type argument. i.e.:

class MyDeserializer extends ProtoDeserializer<MyMessage> {...}
Germ answered 23/5, 2017 at 13:23 Comment(1)
Seems that I'll stick to the constructor-argument option.Fixation

© 2022 - 2025 — McMap. All rights reserved.