Java client fails to deserialize WorkflowTemplate
Checklist
- [x] Double-checked my configuration.
- [x] Tested using the latest version.
- [x] Used the Emissary executor.
Summary
What happened/what you expected to happen? I'm using the java-client to get a WorkflowTemplate (any) form the server. This results in an IllegalStateException.
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 165 path $.metadata.creationTimestamp
at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:385) ~[gson-2.9.0.jar:na]
the server returns a valid JSON with the following metadata (double checked that via cURL):
"metadata": {
"name": "********",
"namespace": "argo",
"uid": "9b1039c0-f710-458e-b510-945337dd4bac",
"resourceVersion": "15460",
"generation": 1,
"creationTimestamp": "2022-06-21T12:57:34Z",
"labels": {
"kustomize.toolkit.fluxcd.io/name": "argo-pipelines",
"kustomize.toolkit.fluxcd.io/namespace": "flux-system"
}
I also checked the schema.json
"creationTimestamp": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time",
"description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"
}
"io.k8s.apimachinery.pkg.apis.meta.v1.Time": {
"description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.",
"format": "date-time",
"type": "string"
}
So GSON understandably expects a BEGIN_OBJECT for the creationTimestamp value.
How can I solve this issue?
What version are you running? Server: v3.3.1 Java Client: v3.3.8
Diagnostics
Paste the smallest workflow that reproduces the bug. We must be able to run the workflow. not applicable, relates to the client.
# Logs from the workflow controller:
kubectl logs -n argo deploy/workflow-controller | grep ${workflow}
# If the workflow's pods have not been created, you can skip the rest of the diagnostics.
# The workflow's pods that are problematic:
kubectl get pod -o yaml -l workflows.argoproj.io/workflow=${workflow},workflow.argoproj.io/phase!=Succeeded
# Logs from in your workflow's wait container, something like:
kubectl logs -c wait -l workflows.argoproj.io/workflow=${workflow},workflow.argoproj.io/phase!=Succeeded
Message from the maintainers:
Impacted by this bug? Give it a 👍. We prioritise the issues with the most 👍.
I also encountered this problem, looking forward to the official solution to it
I also faced this issue.
An easy way to reproduce it is to mock the response from a server and pass it through io.argoproj.workflow.JSON
My example:
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import io.argoproj.workflow.JSON;
import io.argoproj.workflow.models.IoArgoprojWorkflowV1alpha1Workflow;
import java.io.StringReader;
import java.lang.reflect.Type;
public class Main {
public static void main(String[] args) {
JSON json = new JSON();
String mockResponseBody = "{\"metadata\":{\"name\":\"test\",\"creationTimestamp\":\"2022-08-01T07:51:04Z\"}}";
JsonReader jsonReader = new JsonReader(new StringReader(mockResponseBody));
Type localVarReturnType = new TypeToken<IoArgoprojWorkflowV1alpha1Workflow>(){}.getType();
json.getGson().fromJson(jsonReader, localVarReturnType);
}
}
Despite the json is valid, the result is:
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 49 path $.metadata.creationTimestamp
Just wondering which version this issue occurs from? Is there any working 3.3.x Argo Workflows Java SDK?
I have the issue with 3.3.8 java SDK
Can you try latest?
It is maybe not the same case for me. I was migrated from an old argoproj-lab SDK client and no 3.x version works for me in fact. I have: Failed making field 'java.time.Instant#seconds' accessible; either change its visibility or write a custom TypeAdapter for its declaring type So it is related to Gson parsing and basic java type protection. I got the same error with "Expected BEGIN_OBJECT but was STRING" after an ugly patch to the jvm: "--add-opens", "java.base/java.time=ALL-UNNAMED",
But I don't know if it is the same issue I don't want to change this issue target. For information my server is version 3.3.8 if it helps.
io.argoproj.workflow.models.IoArgoprojWorkflowV1alpha1Workflow has an attribute metadata which is an object of io.kubernetes.client.openapi.models.V1ObjectMeta
In turn, V1ObjectMeta has creationTimestamp which is custom DateTime format org.joda.time.DateTime.
So, the reason of the error is that io.argoproj.workflow.JSON can't deserialize this joda creationTimestamp, since there is no proper type adapter registered for it.
Proposing fix taken from here https://stackoverflow.com/questions/15972856/json-using-gson-library-error-expected-begin-object-but-was-string:
In io.argoproj.workflow.JSON in constructor:
public JSON() {
gson = createGson()
.registerTypeAdapter(Date.class, dateTypeAdapter)
.registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter)
.registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter)
.registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
.registerTypeAdapter(byte[].class, byteArrayAdapter)
.registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {
@Override
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new DateTime(json.getAsString());
}
}).create();
}
Version 3.3.9 solves the issue for me.
It seems that it was fixed after I moved to v3.3.9 but I still have to use --add-opens java.base/java.time=ALL-UNNAMED Is there a way around that? I'm using Java 17.
After I used v3.3.9, I found that there was still a serialization problem. It was not the original DateTime problem but the java.time.Instant problem. I created a new package io.argoproj.workflow in my project, which added JSON.java, then copy io.argoproj.workflow.JSON in the sdk source code and modify it in my JSON.java, and modify the JSON constructor as follows:
public class JSON {
//add
private InstantTypeAdapter instantTypeAdapter = new InstantTypeAdapter();
//edit
public JSON() {
gson = createGson()
.registerTypeAdapter(Date.class, dateTypeAdapter)
.registerTypeAdapter(Instant.class, instantTypeAdapter)
.registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter)
.registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter)
.registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
.registerTypeAdapter(byte[].class, byteArrayAdapter)
.create();
}
//add
public static class InstantTypeAdapter extends TypeAdapter<Instant> {
private Instant dateFormat;
public InstantTypeAdapter() {
}
public InstantTypeAdapter(Instant instant) {
this.dateFormat = dateFormat;
}
public void setFormat(Instant instant) {
this.dateFormat = dateFormat;
}
@Override
public void write(JsonWriter out, Instant date) throws IOException {
if (date == null) {
out.nullValue();
} else {
out.value(FORMATTER.format(date));
}
}
@Override
public Instant read(JsonReader in) throws IOException {
try {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
if (date == null) {
return null;
}
return FORMATTER.parse(date, Instant::from);
}
} catch (IllegalArgumentException e) {
throw new JsonParseException(e);
}
}
}
}
}
@alexec could the fix described by @sanqiuli be included in the next release? I can also open a new issue if needed.