Unable to serialize complex (custom) extension in the expected json format
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
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.
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.
This expected json for extension was working fine with CloudEvent 1.0 , and ExtensionFormat / InMemoryFormat marshaling / unmarshaling feature.
+1
+1
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
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
- JsonMappingException if using a custom extension for a object
- 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
-------- 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()));
Hi @pierDipi, is there any update on this? We are blocked with our java17 upgrade due to this.
Hi @pierDipi, checking up on it, did you get a chance to look into it?