azure-functions-java-worker
azure-functions-java-worker copied to clipboard
Fails To Parse eventTime (Event Grid event)
Event Grid triggered Function fails to deserialize the eventTime field coming in this format "eventTime": "2017-06-26T18:41:00.9584103Z". As a workaround I had to use a custom deserializer but this workaround won't work with a POJO.
Repro steps
Code:
@FunctionName("EventGridTrigger_String")
public void run(@EventGridTrigger(name = "eventGridEvent") String message, final ExecutionContext context) {
context.getLogger().info("Java Event Grid trigger (String) function executed.");
EventGridEvent event = gson.fromJson(message, EventGridEvent.class);
context.getLogger().info("Event content: ");
context.getLogger().info("Subject: " + event.subject());
context.getLogger().info("Time: " + event.eventTime().toString());
context.getLogger().info("Id: " + event.id());
context.getLogger().info("Data: " + event.data());
}
Expected behavior
to be able to parse the event sent.
Actual behavior
Fails to parse with error:
2020-06-18T08:59:26.125 [Information] Executing 'Functions.eventGridMonitorString' (Reason='EventGrid trigger fired at 2020-06-18T08:59:26.0921276+00:00', Id=9a0511ce-c3a4-4e0f-aa52-fdb5ab0117c5) 2020-06-18T08:59:26.471 [Information] Java Event Grid trigger (String) function executed. 2020-06-18T08:59:27.660 [Error] Executed 'Functions.eventGridMonitorString' (Failed, Id=9a0511ce-c3a4-4e0f-aa52-fdb5ab0117c5) Result: Failure Exception: IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 22 column 17 path $.eventTime Stack: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.microsoft.azure.functions.worker.broker.JavaMethodInvokeInfo.invoke(JavaMethodInvokeInfo.java:22) at com.microsoft.azure.functions.worker.broker.JavaMethodExecutorImpl.execute(JavaMethodExecutorImpl.java:54) at com.microsoft.azure.functions.worker.broker.JavaFunctionBroker.invokeMethod(JavaFunctionBroker.java:53) at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:33) at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:10) at com.microsoft.azure.functions.worker.handler.MessageHandler.handle(MessageHandler.java:45) at com.microsoft.azure.functions.worker.JavaWorkerClient$StreamingMessagePeer.lambda$onNext$0(JavaWorkerClient.java:92) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 22 column 17 path $.eventTime at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:226) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222) at com.google.gson.Gson.fromJson(Gson.java:927) at com.google.gson.Gson.fromJson(Gson.java:892) at com.google.gson.Gson.fromJson(Gson.java:841) at com.google.gson.Gson.fromJson(Gson.java:813) at samplepackage.Function.run(Function.java:20) ... 16 more Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 22 column 17 path $.eventTime at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:385) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:215) ... 23 more
Known workarounds
Use a custom deserializer.
private final static Gson gson = new GsonBuilder().registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {
@Override
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new DateTime(json.getAsString());
}
}).create();
Related information
Provide any related information
- Programming language used Java 8
- Simple repo to repro https://github.com/Sarah-Aly/Java-Parsing-Bug
- Bindings used Event Grid binding with Event Grid SDK version: 1.4.0-beta.1
If custom Gson instance cannot be created, then the alternate approach is to use the @JsonAdapter annotation on the datetime field as shown below. Let me know if this helps.
public class GsonParser {
public static void main(String[] args) {
String json = "{\"creationTime\": \"2017-06-26T18:41:00.9584103Z\", \"message\": \"Hello world!\" }";
Gson gson = new Gson();
MyObject obj = gson.fromJson(json, MyObject.class);
System.out.println(obj.getMessage() + " " + obj.getCreationTime().dayOfWeek().getAsText());
}
}
class DateTimeDeserializer implements JsonDeserializer<DateTime> {
@Override
public DateTime deserialize(JsonElement json, Type type, JsonDeserializationContext context)
throws JsonParseException {
return new DateTime(json.getAsString());
}
}
class MyObject {
@JsonAdapter(DateTimeDeserializer.class)
private DateTime creationTime;
private String message;
public DateTime getCreationTime() {
return creationTime;
}
public void setCreationTime(DateTime creationTime) {
this.creationTime = creationTime;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Console Output:
Hello world! Monday
Adding a bit more context here from offline discussion with @Sarah-Aly. This is really a problem when you try to write a function trigger that looks like this:
@FunctionName("EventGridTrigger_POJO")
public void eventGridTriggerPOJO(@EventGridTrigger(name = "event") EventGridEvent event, final ExecutionContext context) {
context.getLogger().info("Event content: ");
context.getLogger().info("Subject: " + event.subject());
context.getLogger().info("Time: " + event.eventTime());
context.getLogger().info("Id: " + event.id());
context.getLogger().info("Data: " + event.data());
}
The problem is that in this case the GSON deserialization happens inside the the functions worker itself (so it can bind to the POJO EventGridEvent type (which comes from the official Azure SDK for EventGrid), (you can see the source for this type here)), and the author of the function doesn't have any real control over this process. I suspect this is made worse because the EventTime property in the POJO is a Joda DateTime, a third party library, which adds another wrinkle to the mix.
@pragnagopa this is more or less the same issue as described here https://github.com/Azure/azure-functions-java-worker/issues/247. Is there some sort of strategy for a path forward here? This is the core model type for EventGrid and I expect that customers will commonly use as the parameter to their function.
From #247 and this issue, it feels like the two workarounds are to either create your own model class that looks like EventGridEvent, except the EventTime field is a String and not a DateTime or to just bind to the entire message as a JSON object (as Sarah does in her above example) and then preform the deserialization yourself, after configuring the serializer to support Joda's DateTime. Are there any other strategies that can be undertaken?
A side note: I've been working with the EventGrid team on an effort to release a newer EventGrid SDK that follows the general Azure SDK guidelines. As part of this work, we plan to release a new version of the SDK for Event Grid and that will have a set of breaking changes around our model types. Critically, we intend to change the type of this EventTime member to OffsetDateTime. I think even with that change we would still have a problem here but perhaps it makes it easier going forward to add support to the java worker to bind to this type, since there isn't a dependency on Joda?
@Sarah-Aly Can you please confirm if you are still experiencing this issue? Looking at this related thread, it seems like you should be able to consume a POJO where EventTime is of type Date.
This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.
I believe I am hitting this issue as well.
I'm using the azure-sdk-bom version 1.2.18 in tandem with azure-functions-java-library version 3.0.0. This is with Java 17.
If I use the function bindings, I'm able to send and receive POJOs - however there are limitations to the bindings that make the EventGrid client more applicable to my use-case.
Since I'm using the later version of the libraries, I'm forced to use something like:
EventGridPublisherClient<EventGridEvent> eventGridEventPublisherClient = new EventGridPublisherClientBuilder()
.endpoint(System.getenv("AZURE_EVENTGRID_EVENT_ENDPOINT"))
.credential(new AzureKeyCredential(System.getenv("AZURE_EVENTGRID_EVENT_KEY")))
.buildEventGridEventPublisherClient();
Unfortunately, private <T> EventGridPublisherClient<T> buildClient(Class<T> eventClass) is marked private, so I can't use my own POJOs there and am forced into the BinaryData ser/de.
When my consumer Function kicks off, this is the error messages I'm seeing:
Microsoft.Azure.WebJobs.Host.FunctionInvocationException: Exception while executing function: Functions.ContentUpdater ---> Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcException: Result: Failure Exception: InaccessibleObjectException: Unable to make field private final java.time.LocalDateTime java.time.OffsetDateTime.dateTime accessible: module java.base does not "opens java.time" to unnamed module @467b684d Stack: com.google.gson.JsonIOException: Failed making field 'java.time.OffsetDateTime#dateTime' accessible; either change its visibility or write a custom TypeAdapter for its declaring type at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:22) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:158) etc etc etc...
Here's my consumer function signature:
@FunctionName("MyConsumerFunction") public void run( @EventGridTrigger(name = "message") EventGridEvent message, final ExecutionContext context)
Is there a workaround here? Will I need to re-architect my application since Azure Functions for Java doesn't play well with the Azure Java SDK?
Thanks.
I believe I am hitting this issue as well.
I'm using the azure-sdk-bom version 1.2.18 in tandem with azure-functions-java-library version 3.0.0. This is with Java 17.
If I use the function bindings, I'm able to send and receive POJOs - however there are limitations to the bindings that make the EventGrid client more applicable to my use-case.
Since I'm using the later version of the libraries, I'm forced to use something like:
EventGridPublisherClient eventGridEventPublisherClient = new EventGridPublisherClientBuilder() .endpoint(System.getenv("AZURE_EVENTGRID_EVENT_ENDPOINT")) .credential(new AzureKeyCredential(System.getenv("AZURE_EVENTGRID_EVENT_KEY"))) .buildEventGridEventPublisherClient();
Unfortunately, private EventGridPublisherClient buildClient(Class eventClass) is marked private, so I can't use my own POJOs there and am forced into the BinaryData ser/de.
When my consumer Function kicks off, this is the error messages I'm seeing:
Microsoft.Azure.WebJobs.Host.FunctionInvocationException: Exception while executing function: Functions.ContentUpdater ---> Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcException: Result: Failure Exception: InaccessibleObjectException: Unable to make field private final java.time.LocalDateTime java.time.OffsetDateTime.dateTime accessible: module java.base does not "opens java.time" to unnamed module @467b684d Stack: com.google.gson.JsonIOException: Failed making field 'java.time.OffsetDateTime#dateTime' accessible; either change its visibility or write a custom TypeAdapter for its declaring type at com.google.gson.internal.reflect.ReflectionHelper.makeAccessible(ReflectionHelper.java:22) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:158) etc etc etc...
Here's my consumer function signature:
@functionName("MyConsumerFunction") public void run( @EventGridTrigger(name = "message") EventGridEvent message, final ExecutionContext context)
Is there a workaround here? Will I need to re-architect my application since Azure Functions for Java doesn't play well with the Azure Java SDK?
Thanks.
Currently I face the same issue...any update for it?