Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for custom WellKnownTypeMarshaller #40

Open
pdepietri opened this issue Nov 10, 2024 · 2 comments
Open

Support for custom WellKnownTypeMarshaller #40

pdepietri opened this issue Nov 10, 2024 · 2 comments

Comments

@pdepietri
Copy link

Thanks you for a great project!

I am trying to use the extended Google types, such as google.type.Date in my project and would like the ability to add custom WellKnownTypeMarshaller in order to serialize dates using ISO 8601 date format instead of the individual fields.

Sample proto:

import "google/type/date.proto";
message DateRage {
  google.type.Date startDate = 1;
  google.type.Date endDate = 2;
}

Produces the following JSON output

{
  "startDate": {
    "year": 1,
    "month": 1,
    "day": 1
  },
  "endDate": {
    "year": 9999,
    "month": 12,
    "day": 31
  }
}

The desired output would be

{
  "startDate": "0001-01-01",
  "endDate": "9999-12-31"
}

Adding support for custom marshallers would require making WrapperMarshaller and WellKnownTypeMarshaller and their constructors public and adding a method to MessageMarshaller builder in order to add custom type marshallers.

Sample custom marshaller for google.type.Date:

import java.io.IOException;
import java.time.LocalDate;
import java.util.Locale;
import org.curioswitch.common.protobuf.json.ParseSupport;
import org.curioswitch.common.protobuf.json.WellKnownTypeMarshaller.WrapperMarshaller;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.TextFormat.ParseException;
import com.google.type.Date;

public class DateMarshaller extends WrapperMarshaller<Date> {

  public static final DateMarshaller INSTANCE = new DateMarshaller();

  DateMarshaller() {
    super(Date.getDefaultInstance());
  }

  @Override
  public void doMerge(JsonParser parser, int unused, Message.Builder messageBuilder)
      throws IOException {
    Date.Builder builder = (Date.Builder) messageBuilder;
    try {
      String val = ParseSupport.parseString(parser);
      LocalDate localDate = LocalDate.parse(val);
      Date date = Date.newBuilder()
          .setYear(localDate.getYear())
          .setMonth(localDate.getMonthValue())
          .setDay(localDate.getDayOfMonth())
          .build();
      builder.mergeFrom(date);
    } catch (ParseException e) {
      throw new InvalidProtocolBufferException("Failed to readValue date: " + parser.getText(),
          new IOException(e));
    }
  }

  @Override
  public void doWrite(Date message, JsonGenerator gen) throws IOException {
    String format = String.format(Locale.ENGLISH, "%1$04d-%2$02d-%3$02d", message.getYear(),
        message.getMonth(), message.getDay());
    gen.writeString(format);
  }

}

MessageMarshaller builder would look something like this:

MessageMarshaller.builder()//
        .register(Date.getDefaultInstance())
        .register(DateRage.getDefaultInstance())
        .register(AuditInfo.getDefaultInstance())
        .addCustomMarshaller(DateMarshaller.INSTANCE)
        .build();
@chokoswitch
Copy link
Contributor

Hi @pdepietri - we support custom marshallers via jackson-databind integration such as this

https://github.com/curioswitch/protobuf-jackson/blob/main/src/testDatabind/java/org/curioswitch/common/protobuf/json/ObjectMapperTest.java

Will that work for you? Also see #12 (comment) for some background on the goals of this library, notably we don't intend for the core to have additional features compared to upstream (we would be happy to implement them after upstream does). For custom marshalling, we found it to work well as an implementation of jackson-databind's abstraction though.

@C0mbatwombat
Copy link

C0mbatwombat commented Nov 15, 2024

@pdepietri we were able to do this after #12 got merged. We use it like this:

SimpleModule customSerializers = new SimpleModule();
JsonSerializer<YourProto> serializer = new StdSerializer<>(YourProto.class) {
                @Override
                public void serialize(YourProto value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                    gen.writeString(serializeToString(value));
                }
            };
JsonDeserializer<YourProto> deserializer = new StdDeserializer<>(YourProto.class) {
                @Override
                public YourProto deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
                    String value = p.getValueAsString();
                    return deserializeFromString(value);
                }
            };
customSerializers.addDeserializer(YourProto.class, deserializer).addSerializer(serializer);

    private static MessageMarshaller marshaller = MessageMarshaller.builder()
            .omittingInsignificantWhitespace(true)
            .preservingProtoFieldNames(true)
            .build();
    public static ObjectMapper mapper = new ObjectMapper().registerModule(MessageMarshallerModule.of(marshaller))
            .registerModule(customSerializers);

Now you can use the Jackson mapper to (de-)serialize proto to json, and have a custom serialization format for YourProto.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants