FasterXML / jackson-dataformats-binary

Uber-project for standard Jackson binary format backends: avro, cbor, ion, protobuf, smile
Apache License 2.0
310 stars 133 forks source link

[Jackson-dataformat-ion - 2.12x] Facing issue while serializing/deserializing #256

Open saikumarsuvanam opened 3 years ago

saikumarsuvanam commented 3 years ago

Below code works with Jackson 2.8 version but with jackson 2.12x (Jackson-dataformat-ion), deserialization test case fails.

Also tried with intializing ionObject mapper by disabling native type ids but it didnt work. IonObjectMapper m = new IonObjectMapper().disable(IonGenerator.Feature.USE_NATIVE_TYPE_ID);

Test case results are : Expected :is <ChildSelector(value=FareCard(elementFactor=FARE, fares=[FARE(type=MARGIN, value=5.00 USD, level=SINGLE)]))> Actual :<ChildSelector(value=null)>

--- TestClass Code ---

import com.amazon.ion.IonValue;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.dataformat.ion.EnumAsIonSymbolSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import com.fasterxml.jackson.core.type.TypeReference;

import javax.annotation.Nonnull;

import java.beans.ConstructorProperties;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collections;

public class Test {

    private static final String TEST_SELECTOR_STR =  "Child::{" +
            "element_factor: FARE," +
            "fares: [{ type: MARGIN, level: SINGLE, value: { amount: \"10.00\", currencyCode: \"USD\" } }]}";
    private static final IonValue TEST_SELECTOR_ION = IonTestUtils.deserializeIon(TEST_SELECTOR_STR);

    private static final ChildSelector<FareCard> TEST_SELECTOR = new ChildSelector<>(TEST_FARE_CARD);

    private static final FareCard TEST_FARE_CARD = new FareCard(Element.FARE, Collections.singletonList(
            Fare.marginFare(new Currency("10.00", "USD"))));

    @Test
    public void testDeserialize() throws Exception {
        Rule<FareCard> rc = NoSQLIonValue.deserialize(TEST_SELECTOR_ION, new TypeReference<ChildSelector<FareCard>>() {});
        assertThat(rc, is(TEST_SELECTOR));
    }

    @Test
    public void testSerialize() throws Exception {
        IonValue ion = NoSQLIonValue.serialize(TEST_SELECTOR);
        assertThat(ion, is(TEST_SELECTOR_ION));
    }
}

---NoSQLIonValue.java---

package com.jacksonissue;
import com.amazon.ion.IonList;
import com.amazon.ion.IonValue;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.ion.IonGenerator;
import com.fasterxml.jackson.dataformat.ion.IonObjectMapper;
import com.fasterxml.jackson.dataformat.ion.ionvalue.IonValueModule;

import com.jacksonissue.NoSQLValue;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.JsonGenerator;
import com.netbeetle.jackson.ConstructorPropertiesAnnotationIntrospector;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class NoSQLIonValue {

    public static <T> T deserialize(@Nonnull IonValue ionValue, TypeReference tr) {
        return fMapper.readValue(ionValue, tr);
    }

    public static <T extends NoSQLValue> IonValue serialize(@Nonnull T obj) {
        return fMapper.writeValueAsIonValue(obj);
    }

    private static IonObjectMapper fMapper = new IonObjectMapper();

    static {
        fMapper.setPropertyNamingStrategy(new NoSQLPropertyNamingStrategy());

        // Lombok sticks @ConstructorProperties on generated constructors - this lets Jackson read it
        fMapper.setConfig(fMapper.getDeserializationConfig().withAppendedAnnotationIntrospector(
                new ConstructorPropertiesAnnotationIntrospector()));

        fMapper.registerModule(new IonValueModule());
        fMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        fMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
        fMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }
}

---Rule.java---

package com.jacksonissue;

 import com.fasterxml.jackson.dataformat.ion.polymorphism.IonAnnotationTypeResolverBuilder;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
    @JsonTypeResolver(IonAnnotationTypeResolverBuilder.class)
    @JsonSubTypes({
            @JsonSubTypes.Type(AnotherSelector.class),
            @JsonSubTypes.Type(ChildSelector.class)
    })
    public interface Rule<T> {

    }

--- ChildSelector.java ---

package com.jacksonissue;

import com.jacksonissue.nosqlvalue.NoSQLValue;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;

import java.io.Serializable;

    @Data
    @EqualsAndHashCode(callSuper = false)
    @JsonTypeName("Child")
    public class ChildSelector<T> extends NoSQLValue implements Rule<T>, Serializable {

        private static final long serialVersionUID = 1L;

        @Getter(onMethod = @__({@JsonValue}))
        private final T value;

        @Override
        public boolean isValid() {
            return true;
        }
    }

---FareCard.java---

package com.jacksonissue;

import com.fasterxml.jackson.dataformat.ion.EnumAsIonSymbolSerializer;
import com.jacksonissue.NoSQLValue;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Value;
import lombok.experimental.NonFinal;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

    @Data
    @NoArgsConstructor(access = AccessLevel.PUBLIC)
    @EqualsAndHashCode(callSuper = false)
    public class FareCard extends NoSQLValue implements Serializable {

        private static final long serialVersionUID = 1L;

        @JsonSerialize(using = EnumAsIonSymbolSerializer.class)
        private Element elementFactor;
        private List<Fare> fares;

        public FareCard(Element elementFactor, List<Fare> fares) {
            this.elementFactor = elementFactor;
            this.fares = fares;
        }
    }

-- FARE.java --

package com.jacksonissue;

import com.jacksonissue.Currency;
import com.fasterxml.jackson.dataformat.ion.EnumAsIonSymbolSerializer;
import com.jacksonissue.NoSQLValue;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

import java.beans.ConstructorProperties;
import java.io.Serializable;
import java.math.BigDecimal;

import static com.google.common.base.Preconditions.checkArgument;

    @Data
    @EqualsAndHashCode(callSuper = false)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Log4j2
    public class Fare extends com.jacksonissue.nosqlvalue.NoSQLValue implements Serializable {

        @RequiredArgsConstructor
        public enum Type {
            BASEMARGIN(Level.MULTIPLE),
            MARGIN(Level.SINGLE);
        }
        @JsonSerialize(using = EnumAsIonSymbolSerializer.class)
        @NonNull
        private Fare.Type type;
        @NonNull
        private Currency value;
       @JsonSerialize(using = EnumAsIonSymbolSerializer.class)
        private Level level;

        @ConstructorProperties({"type", "value", "level"})
        public Fare(@NonNull Fare.Type type, @NonNull Currency value, @NonNull Level level) {

            this.type = type;
            this.value = value;
            this.level = level;

        }
        public static Fare marginFare(@NonNull Currency value) {
            return new Fare(Fare.Type.MARGIN, value, Level.SINGLE);
        }
 }

-- Currency.java --

package com.jacksonissue;
import java.io.Serializable;
import java.math.BigDecimal;

  public final class Currency implements Comparable, Serializable {
      private static final long serialVersionUID = -351418430273563584L;
      private static final BigDecimal ZERO = new BigDecimal("0");
      private final BigDecimal amount_;
      private final String currencyCode;
      public Currency(BigDecimal amount, String currencyCode) {
              this.amount_ = amount;
              this.currencyCode = currencyCode;
      }
      public Currency(String amount, String currencyCode) {
          this(amount == null ? null : new BigDecimal(amount), currencyCode);
      }
  }

---Level.java ---

package com.jacksonissue;

    public enum Level {
       MULTIPLE, SINGLE;
    }

--Element.java--

package com.jacksonissue;

    public enum Element {
        FARE,
        SUM;
    }

--NoSQLValue.java--

package com.jacksonissue;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

        @JsonIgnoreProperties(ignoreUnknown = true)
        public abstract class NoSQLValue {

            @JsonIgnore
            public abstract boolean isValid();

            @Override
            public String toString() {
                return ReflectionToStringBuilder.toString(this);
            }

        }

-- IonTestUtils.java --

package com.jacksonissue;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import lombok.experimental.UtilityClass;

        @UtilityClass
        public final class IonTestUtils {
            private static IonSystem ionSystem;
            public static synchronized IonSystem getIonSystem() {
                if (ionSystem == null) {
                    ionSystem = IonSystemBuilder.standard().build();
                }
                return ionSystem;
            }
            @SuppressWarnings("unchecked")
            public static <T extends IonValue> T deserializeIon(final String ionText) {
                return (T) getIonSystem().singleValue(ionText);
            }
        }

--sym--

  $ion_shared_symbol_table::{
          name:"sym",
          version:1,
          symbols:[
           "Child",
           "element_factor",
           "FARE",
          "level",
          "SINGLE",
          "MULTIPLE",
          "SUM",
          "amount",
          "currencyCode",
          "value"
          ],
   }