forestwanglin / okx-v5-java

OKX V5 SDK for JAVA
MIT License
14 stars 5 forks source link

The return value of an enumeration variable may be empty! #2

Closed jouzitong closed 4 months ago

jouzitong commented 4 months ago

I discovered during actual application that when the enumeration of the return value is empty, an exception is thrown.

Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot coerce empty String ("") to `xyz.felh.okx.v5.enumeration.TriggerPxType` value (but could if coercion was enabled using `CoercionConfig`)
 at [Source: (okio.RealBufferedSource$inputStream$1); line: 1, column: 625] (through reference chain: ai.zzt.okx.okx_client.client.vo.RList["data"]->java.util.ArrayList[0]->ai.zzt.okx.okx_client.client.resp.AlgoOrder["slTriggerPxType"])
    at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadCoercion(DeserializationContext.java:1817) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer._checkCoercionFail(StdDeserializer.java:1662) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._deserializeAltString(EnumDeserializer.java:364) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.std.EnumDeserializer._fromString(EnumDeserializer.java:279) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:248) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:314) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177) ~[jackson-databind-2.15.4.jar:2.15.4]
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:359) ~[jackson-databind-2.15.4.jar:2.15.4]
jouzitong commented 4 months ago

You can refer to the registerModule method in com.fasterxml.jackson.databind.ObjectMapper. Enumeration values can be processed uniformly here. I simply implemented a Module interface, you can also refer to it:

public class CustomEnumModule extends SimpleModule {

    public CustomEnumModule() {
        addDeserializer(Enum.class, new StatusDeserializer());
    }

    private static class StatusDeserializer extends JsonDeserializer<Enum> {
        @Override
        public Enum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            JsonNode node = p.getCodec().readTree(p);
            if (node.isNull()) {
                return null; // Return UNKNOWN for null value
            }
            String value = node.asText();
            if (value==null||value.isEmpty()) {
                return null; // Return UNKNOWN for empty value
            }

            Object currentValue = ctxt.getParser().getParsingContext().getCurrentValue();
            String currentName = ctxt.getParser().getParsingContext().getCurrentName();
            Class<?> valueClass = currentValue.getClass();
            Class<?> type;
            try {
                Field declaredField = valueClass.getDeclaredField(currentName);
                type = declaredField.getType();
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
            if (!type.isEnum()) {
                throw new RuntimeException("type is not enum");
            }
            Class<Enum> enumClass = (Class<Enum>) type;

            Enum[] enumConstants = enumClass.getEnumConstants();
            for (Enum enumConstant : enumConstants) {
                if (enumConstant.name().equals(value)) {
                    return enumConstant;
                }
            }
            return null;
        }
    }
}
forestwanglin commented 4 months ago

I cannot reproduce what you sent above.

I get slTriggerPxType = null even if I pass slTriggerPxType: 'ABC' that is not a value of TriggerPxType.

Could you please paste your case there?

jouzitong commented 4 months ago

OkxApiService uses ObjectMapper when serializing objects. That problem also lies in serialization, so the main problem is to solve the enumeration serialization problem. Look at the example below:

 public static ObjectMapper defaultObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
        return mapper;
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper objectMapper = defaultObjectMapper();
        Order order = objectMapper.readValue("{\"tpTriggerPxType\": \"\"}", Order.class);
        System.out.println(order);
    }

In this example, the same error can also be prompted.

jouzitong commented 4 months ago

The interface I implemented specifically is as follows: https://www.okx.com/docs-v5/zh/?shell#order-book-trading-algo-trading-get-algo-order-list. This interface will have many parameters, and there will also be many null-valued parameters.

forestwanglin commented 4 months ago

I tested it using websocket at first so I cannot reproduce your case.

I've fixed this issue in version 0.3.2024051001 when using rest api by adding configuration as below.

mapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);

The enumeration field will be set to NULL if any unknown string value, including "".