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

Unable to serialize complex (custom) extension in the expected json format #667

Open
abhupadh opened this issue Sep 13, 2024 · 6 comments
Open

Comments

@abhupadh
Copy link

abhupadh commented Sep 13, 2024

creating a custom extension object implementing CloudEventExtension, when using the extension (customExt) as below, and then serializing the CloudEvent, the serialization for the CloudEvent is failing with JsonMappingException.

CloudEventBuilder.v1()
        .withSource(URI.create(source))
        .withType(event_type)
        .withTime(event_time)
        .withId(event.getEventId())
        .withDataContentType(contentType)
        .withData(eventPayaload)
        .withDataSchema(URI.create(XDM_SCHEMA_URI))
        .withExtension(DATA_SCHEMA_VERSION_KEY_EXTENSION_NAME, schemaVerExt)
        .withExtension(X_ACTION_ID_KEY_EXTENSION_NAME, requestIdExt)
        .withExtension(customExt);

here is how the extension looks like. The customExt is the Recipient object
Screenshot 2024-09-06 at 4 22 54 PM

Also, if using the other supported methods withExtension (with key, value) as below, I had to send the string version of the customExt, and then if serialized the output is not in the expected format (see below). It can break our customers event
integrations. Kindly suggest.

CloudEventExtension_Methods

expected serialized CloudEvent custom extension

"recipient":{"userid":"9CE048DB5E8F485XXXXXXXXX@AdobeOrg"}

actual serialized CloudEvent custom extension

"recipient":"{\"userid\":\"9CE048DB5E8F485XXXXXXXXX@AdobeOrg\"}"

The serialization is failing while reading extensions here, as I believe the Object type value is not supported.

@abhupadh
Copy link
Author

abhupadh commented Sep 13, 2024

This expected json for extension was working fine with CloudEvent 1.0 , and ExtensionFormat / InMemoryFormat marshaling / unmarshaling feature.

@shikhartanwar
Copy link

+1

1 similar comment
@nicdard
Copy link

nicdard commented Sep 19, 2024

+1

@pierDipi
Copy link
Member

In the CloudEvents spec, the type system for metadata https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#type-system describes the type "String" but what you expect is a JSON value ?

"recipient":{"userid":"9CE048DB5E8F485XXXXXXXXX@AdobeOrg"}

Also please, add the full steps to reproduce the issue that I can and paste

@abhupadh
Copy link
Author

abhupadh commented Oct 1, 2024

Thanks @pierDipi for responding. Yes, that is the correct expectation (receiving a json value). Below is the code and steps to reproduce the issues while trying both the ways of setting an extension to a CloudEvent

  1. JsonMappingException if using a custom extension for a object
  2. Unexpected string instead of Json for a complex extension

------- Issue 1 ---------
Recipient object

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class Recipient {
  @JsonProperty("userid")
  private final String userid;

  @JsonProperty("clientid")
  private final String clientid;

  public Recipient(String userid) {
    this.userid = userid;
    this.clientid = null;
  }

  @JsonCreator
  public Recipient(
      @JsonProperty("userid") String userid, @JsonProperty("clientid") String clientid) {
    this.userid = userid;
    this.clientid = clientid;
  }

  // ------------------------------------------------------------------------------------------------
  // Accessors
  // ------------------------------------------------------------------------------------------------

  public String getUserid() {
    return userid;
  }

  public String getClientid() {
    return clientid;
  }

  // ------------------------------------------------------------------------------------------------

  @Override
  public String toString() {
    return "Recipient{" + "userid='" + userid + '\'' + ", clientid='" + clientid + '\'' + '}';
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Recipient recipient = (Recipient) o;
    return Objects.equals(userid, recipient.userid) && Objects.equals(clientid, recipient.clientid);
  }

  @Override
  public int hashCode() {
    return Objects.hash(userid, clientid);
  }
}

Creating a custom extension RecipientExtension using the Recipient object

public class RecipientExtension implements CloudEventExtension {

  public static final String RECIPIENT_KEY_EXTENSION_NAME = "recipient";
  private static final Set<String> KEY_SET = Set.of(RECIPIENT_KEY_EXTENSION_NAME);

  private Recipient recipient;

  public RecipientExtension(String userId, String clientId) throws IOException {
    this.recipient = new Recipient(userId, clientId);
  }

  public Recipient getRecipient() {
    return recipient;
  }

  @Override
  public void readFrom(CloudEventExtensions cloudEventExtensions) {
    Object tp = cloudEventExtensions.getExtension(RECIPIENT_KEY_EXTENSION_NAME);
    if (tp != null) {
      this.recipient = tp instanceof Recipient ? (Recipient) tp : null;
    }
  }

  @Override
  public Object getValue(String key) throws IllegalArgumentException {
    switch (key) {
      case RECIPIENT_KEY_EXTENSION_NAME:
        return this.recipient;
      default:
        throw ExtensionUtils.generateInvalidKeyException(this.getClass(), key);
    }
  }

  @Override
  public Set<String> getKeys() {
    return KEY_SET;
  }

  @Override
  public String toString() {
    return "RecipientExtension{" + "recipient=" + recipient + '}';
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    RecipientExtension that = (RecipientExtension) o;
    return Objects.equals(recipient, that.recipient);
  }

  @Override
  public int hashCode() {
    return Objects.hash(recipient);
  }
}

Create the CloudEvent object using CloudEventBuilder.v1()

private static CloudEvent getCloudEvent()
      throws JsonProcessingException {
    return CloudEventBuilder.v1()
        .withSource(URI.create("some_source"))
        .withType("some_event_type")
        .withId("some_id")
        .withDataContentType("application/json")
        .withData(JsonCloudEventData.wrap(convertToJsonNode("some_data")))
        .withDataSchema(URI.create("some_schema"))
        .withExtension("requestidext", "some_request_id")
        .withExtension(new RecipientExtension(userId, null))
        .build();
  }

Now serialize the CloudEventV1 object

public static final SimpleModule simpleModule =
      getCloudEventJacksonModule(JsonFormatOptions.builder().build());

  private static final ObjectMapper OBJECT_MAPPER =
      JsonMapper.builder()
          .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
          .serializationInclusion(Include.NON_NULL)
          .addModule(
              simpleModule)
          .build();
          
          
public static String serialize(final CloudEvent object) throws JsonProcessingException {
    Preconditions.checkNotNull(object, THE_OBJECT_INSTANCE_CAN_NOT_BE_NULL);
    return OBJECT_MAPPER.writeValueAsString(object);
 }

the above serialization will fail with JsonMappingException with the underlying stacktrace showing failing at the readContext of CloudEventSerializer
Screenshot 2024-10-01 at 11 32 36 AM
Screenshot 2024-10-01 at 11 33 23 AM

-------- Issue 2 --------
Instead of using the custom extension object, if we use the serialized string of it using
withExtension(String key, String value) we are not getting the json representation of it

var recipientExt = new RecipientExtension(userId, clientId);

get the Recipient object using the recipientExt.getRecipient() and set as extension while constructing CloudEvent with its serialized version

.withExtension("recipient", serialize(recipientExt.getRecipient()));

@abhupadh
Copy link
Author

Hi @pierDipi, is there any update on this? We are blocked with our java17 upgrade due to this.

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

4 participants