Calling parseFrom() method for generic protobuffer class in java
Asked Answered
E

5

9

I'm calling an api to get the an input stream and then call static method parseFrom(inputstream) to convert it to the protobuffclass.

If I do it with a specific class it works:

public CustomerDTOOuterClass.CustomerDTO GetCustomer()
{
    CustomerDTOOuterClass.CustomerDTO customer = null;
    try
    {
        URL url = new URL("https://localhost:44302/Api/customer/1?");

        HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Content-Type", "application/x-protobuf");
        conn.connect();

        InputStream is = conn.getInputStream();

        CustomerDTOOuterClass.CustomerDTO customer =
                CustomerDTOOuterClass.CustomerDTO.parseFrom(is);

        conn.disconnect();
    }
    catch(Exception ex)
    {
        System.out.println("[ "+ex.getMessage()+" ]");
    }

    return customer;
}

but if I change it to generic type it fails because T doesn't have the method parseFrom, is there any interface I could implement in T so I can call the parseFrom method?

public T GetObject()
{
    T object = null;
    try
    {
        URL url = new URL("https://localhost:44302/Api/customer/1?");

        HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Content-Type", "application/x-protobuf");
        conn.connect();

        InputStream is = conn.getInputStream();

        T object = T.parseFrom(is);

        conn.disconnect();
    }
    catch(Exception ex)
    {
        System.out.println("[ "+ex.getMessage()+" ]");
    }

    return object;
}

this is the error I get: Error:(68, 27) error: cannot find symbol method parseFrom(InputStream)

Enchorial answered 24/12, 2014 at 20:46 Comment(3)
Two things found with quick search, maybe one helps: #4035527 and transylvania-jug.org/archives/365Lampyrid
Can you tell us what you trying to achieve. You could look at DynamicMessage's if your processing is very generic. Alternatively you could pass in a Builder class to getObject() method and use the mergeFrom method of the builder classPlutonium
You cant really do this? Serialized protos don't know their own type.Bounce
T
12

Every generated protobuf type contains a static member called PARSER which is an implementation of the com.google.protobuf.Parser<T> interface. Your getObject method simply needs to take a Parser<T> as a parameter. So then you'd call it like:

Foo foo = getObject(Foo.PARSER);
Throat answered 26/12, 2014 at 1:36 Comment(1)
Good answer, I was interested in how this would play out so I hacked around with it a bit and expanded in a separate answer, hope you don't mind!Lampyrid
B
9

If you want to do this for T, it's easier and more natural to pass the Class<T> (i.e. the class of the Proto type) into the constructor of your class, and then obtain the Parser from that like this:

public class Thing<T extends Message> {
    final Parser<T> parser;

    Thing(Class<T> cls) {
        parser = (Parser<T>) cls.getMethod("parser").invoke(null);
    }

    T deserialize(byte[] bytes) {
        parser.parseFrom(bytes);  // try/catch etc
    }
}
Bloodstock answered 13/11, 2015 at 20:25 Comment(0)
L
1

To expand on Kenton Varda's answer:

First I'd refactor your method into separate methods for getting the input stream and parsing it. Only the latter has any reason to be generic.

public InputStream getInputStream() {
  // get it
}

Now you intend to parse the input stream and build a POJO from the protobuf. It's reasonable IMO to expect that at this point your code must be aware of what type of object you're going to get, because otherwise how would you do something intelligent with it next? E.g.

InputStream is = getInputStream();
Object o = parseGenericInputStream(is);
doSomethingWithParsedObject(o); // how to do this if you don't know o's type?

You reasonably must know o's type once you've parsed it (and therefore before you parse it), otherwise you can't do anything meaningful with it that I can think of.

So... again with credit to Kenton Varda:

public void doStuff() {
  ...
  InputStream is = getInputStream();
  MyProtobufClass pojo = parseGenericInputStream(MyProtobufClass.PARSER, is);
  doSomethingWithParsedObject(pojo);
  ...
}

private <T> T parseGenericInputStream(Parser<T> parser, InputStream inputStream)
    throws InvalidProtocolBufferException {
  return parser.parseFrom(inputStream);
}

At this point though you're writing a generic method for one line of code, which is kind of not worth it if you ask me.

Lampyrid answered 26/12, 2014 at 3:49 Comment(0)
B
0

No, there is not; you cannot deserialize a proto without knowing its type.

If you do know its type, then you can pass in a Builder for its type, however.

(Additionally, you can't call static methods on a type variable like T.)

Bounce answered 24/12, 2014 at 23:5 Comment(0)
J
0

Two steps:

  1. Create a functional interface that throws IOException just as MyProtobufClass.Entity::parseFrom does.
  2. Use this interface within a generic load method.
    @FunctionalInterface
    public interface ParserFunction<T, R> {
        R apply(T t) throws IOException;
    }

    public class Loader {
    
        public static <T> T loadPbFile(
            String path,
            ParserFunction<FileInputStream, T> parser) throws IOException {
            FileInputStream inputStream = getFileInputStream(path);
            return parser.apply(inputStream);
        }
    }

Now simply use MyProtobufClass.Entity::parseFrom as second Parameter of the generic function.

Jessejessee answered 22/3, 2024 at 10:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.