Set of support modules for Java 8 datatypes (Optionals, date/time) and features (parameter names)

Overview

Overview

This is a multi-module umbrella project for Jackson modules needed to support Java 8 features, especially with Jackson 2.x that only requires Java 7 for running (and until 2.7 only Java 6).

Jackson 2.x

When used with Jackson 2.x, Java 8 support is provided via 3 separate modules:

  • Parameter names: support for detecting constructor and factory method ("creator") parameters without having to use @JsonProperty annotation
    • provides com.fasterxml.jackson.module.paramnames.ParameterNamesModule
  • Java 8 Date/time: support for Java 8 date/time types (specified in JSR-310 specification)
    • provides com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
    • ALSO provides legacy variant com.fasterxml.jackson.datatype.jsr310.JSR310TimeModule
    • difference between 2 modules is that of configuration defaults: use of JavaTimeModule strongly recommended for new code
  • Java 8 Datatypes: support for other new Java 8 datatypes outside of date/time: most notably Optional, OptionalLong, OptionalDouble
    • provides com.fasterxml.jackson.datatype.jdk8.Jdk8Module

all of which are built from this repository, and accessed and used as separate Jackson modules (with separate Maven artifacts).

Jackson 3.0

Jackson 3.0 changes things as it requires Java 8 to work and can thereby directly supported features.

Because of this parameter-names and datatypes modules are merged into jackson-databind and need not be registered; datetime module (JavaTimeModule) remains separate module due to its size and configurability options.

So you will only need to separately add "Java 8 Date/time" module (see above for description)

License

All modules are licensed under Apache License 2.0.

Status

Build Status Tidelift

Usage

Maven dependencies

To include modules, you use some or all of:

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

and either include versions directly, OR, preferably, import Jackson BOM that will specify consistent version set.

Note that the parent project -- jackson-modules-java8 -- is ONLY used as parent pom by individual "child" modules, and DOES NOT have dependencies on them. This means that you should not depend on it as that will not include child modules.

Registering modules

The most common mechanism (and one recommended by Jackson team) is to explicitly register modules you want. This is done by code like:

// Up to Jackson 2.9: (but not with 3.0)
ObjectMapper mapper = new ObjectMapper()
   .registerModule(new ParameterNamesModule())
   .registerModule(new Jdk8Module())
   .registerModule(new JavaTimeModule()); // new module, NOT JSR310Module

// with 3.0 (or with 2.10 as alternative)
ObjectMapper mapper = JsonMapper.builder() // or different mapper for other format
   .addModule(new ParameterNamesModule())
   .addModule(new Jdk8Module())
   .addModule(new JavaTimeModule())
   // and possibly other configuration, modules, then:
   .build();

Alternatively, you can also auto-discover these modules with:

ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();

Regardless of registration mechanism, after registration all functionality is available for all normal Jackson operations.

Notes on Registration

But do note that you should only either explicit OR automatic registration: DO NOT combine explicit and auto-registration. If you use both, only one of registrations will have effect. And selection of which one varies by module and settings:

  • If MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS is defined, the FIRST registration succeeds, rest ignored
    • Duplicates are detected using id provided by Module.getTypeId(); duplicate-detection requires that Module provides same for all instances (true for Modules provided by this repo)
  • Otherwise all registrations are processed by the LAST one has effect as it has precedence over earlier registrations.

Also note that before Jackson 2.10, auto-registration would only register older JSR310Module, and not newer JavaTimeModule -- this is due to backwards compatibility. This was changed in Jackson 2.10.

If you want "the other" version of the module but also use auto-registration, make sure to register "other" module explicitly AFTER calling mapper.findAndRegisterModules(). Call after works because getTypeId() provided by modules differs so they are not considered duplicates.

Development

Maintainers

Following developers have committer access to this project.

  • Authors
    • Nick Williams ([email protected]) contributed Java 8 date/time module; still helps issues from time to time
    • Tatu Saloranta (@cowtowncoder) wrote the other 2 modules and maintains them for 2.x (in 3.0, integrated into core jackson-databind)
  • Maintainers:
    • Michael O'Keeffe ([email protected]) is the current maintainer of Java 8 date/time module

More

See Wiki for more information (javadocs).

Issues
  • Unannotated single-argument constructor / factory method not considered a creator

    Unannotated single-argument constructor / factory method not considered a creator

    Take the following domain class on Java 8 compiled with -parameters:

    class SingleValueWrapper {
    
    	private final Integer integer;
    
    	public SingleOtherValue(Integer integer) {
    		this.integer = integer;
    	}
    }
    

    This test:

    @Test
    public void testname() throws Exception {
    
    	ObjectMapper mapper = new ObjectMapper();
    	mapper.registerModule(new ParameterNamesModule());
    
    	SingleValueWrapper value = mapper.readValue("{ \"integer\" : 2 }", SingleValueWrapper.class);
    
    	assertThat(value, is(notNullValue()));
    }
    

    fails with:

    com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.example.LombokImmutablesTests$SingleValueWrapper` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
     at [Source: (String)"{ "integer" : 2 }"; line: 1, column: 3]
    	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
    	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1342)
    	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1031)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1290)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
    	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
    	at com.example.LombokImmutablesTests.testname4(LombokImmutablesTests.java:73)
    	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 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
    

    A couple of more observations:

    • Adding an explicit annotation (e.g. @ConstructorProperties) makes the test go green. Unfortunately I don't control the class so that I cannot add any annotations. Also, I thought adding the parameter names module should be sufficient as a name can now be derived from the class and additional annotations shouldn't be needed. I.e. the annotation would just redeclare what's already defined.
    • Adding an additional property makes the test pass. This is probably what strikes me most about the problem as it's complete unintuitive why a constructor wouldn't work for one parameter, but would for more than one.
    • The same applies if you rather expose a factory method than a constructor.
    • Even configuring a Mode explicitly doesn't change anything about the failing test.
    • The execution at some point invokes findNameForDeserialization(…) which ´ParameterNamesAnnotationIntrospector` does not override. Is that by design? Because if I go ahead and implement that method like this:
    @Override
    public PropertyName findNameForDeserialization(Annotated a) {
    
    	if (a instanceof AnnotatedParameter) {
    		return PropertyName.construct(findParameterName((AnnotatedParameter) a));
    	}
    
    	return super.findNameForDeserialization(a);
    }
    

    things start to work again. I can see that this method is supposed to explicitly obtain a name from an annotation but it feels like that path is favored for a one-argument constructor.

    opened by odrotbohm 26
  • `DurationDeserializer` should use `@JsonFormat.pattern` (and config override) to support configurable `ChronoUnit`

    `DurationDeserializer` should use `@JsonFormat.pattern` (and config override) to support configurable `ChronoUnit`

    When deserializing DurationDeserializer treats all durations of number values as seconds and delegates string values to Duration.parse.

    Versions

    com.fasterxml.jackson.core:jackson-core:jar:2.11.2
    com.fasterxml.jackson.core:jackson-databind:jar:2.11.2
    com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.2
    com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.2
    com.fasterxml.jackson.core:jackson-annotations:jar:2.11.2
    

    Example

    class A {
        @JsonFormat(pattern = "h")
        Duration dur;
    
        public static void main(String[] args) throws Exception {
            JsonMapper m = JsonMapper.builder()
                    .addModule(new JavaTimeModule())
                    .build();
            A a0 = m.readValue("{\"dur\":2}".getBytes(), A.class);
            System.out.println(a0.dur.equals(Duration.ofHours(2))); // should print "true" but prints "false
    
            A a1 = m.readValue("{\"dur\":\"2\"}".getBytes(), A.class);
            System.out.println(a1.dur.equals(Duration.ofHours(2))); // should print "true" but throws Exception
        }
    }
    

    The Exception thrown by the second readValue call:

    com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.Duration` from String "2": Failed to deserialize java.time.Duration: (java.time.format.DateTimeParseException) Text cannot be parsed to a Duration
     at [Source: (byte[])"{"dur":"2"}"; line: 1, column: 8] (through reference chain: *****)
    
    	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
    	at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1702)
    	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:947)
    	at com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase._handleDateTimeException(JSR310DeserializerBase.java:129)
    	at com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer.deserialize(DurationDeserializer.java:92)
    	at com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer.deserialize(DurationDeserializer.java:43)
    	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:156)
    	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4524)
    	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3527)
            ...
    Caused by: java.time.format.DateTimeParseException: Text cannot be parsed to a Duration
    	at java.base/java.time.Duration.parse(Duration.java:417)
    	at com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer.deserialize(DurationDeserializer.java:90)
    	... 70 more
    

    Even though JsonFormat doesn't explicitly mention any jsr310 classes in its documentation they should still work with it. Especially since DurationDeserializer already respects JsonFormat#lenient.

    2.12 json-format 
    opened by chisui 25
  • `ParameterNamesModule` does not deserialize with a single parameter constructor when using `SnakeCase` `PropertyNamingStrategy`

    `ParameterNamesModule` does not deserialize with a single parameter constructor when using `SnakeCase` `PropertyNamingStrategy`

    Since release 2.9.2, the following test that passes with previous versions is broken.

    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.PropertyNamingStrategy;
    
    import org.junit.Test;
    
    import static org.assertj.core.api.BDDAssertions.*;
    
    public class JsonCreatorSnakeCaseNamingTest
    {
    	@Test
    	public void shouldDeserializeClassWithJsonCreatorWithSnakeCaseNaming() throws Exception {
    
    		// given
    		ObjectMapper objectMapper = new ObjectMapper();
    		objectMapper.registerModule(new ParameterNamesModule());
    		objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
    
    		// when
    		String json = "{\"first_property\":\"1st\"}";
    		ClassWithOneProperty actual = objectMapper.readValue(json, ClassWithOneProperty.class);
    
    		then(actual).isEqualToComparingFieldByField(new ClassWithOneProperty("1st"));
    	}
    
    	static class ClassWithOneProperty {
    		public final String firstProperty;
    
    		@JsonCreator
    		public ClassWithOneProperty(String firstProperty) {
    			this.firstProperty = firstProperty;
    		}
    	}
    }
    

    The test passes with if I create the ParameterNamesModule with JsonCreator.Mode.PROPERTIES as the argument.

    Is this intended? If so, this should probably be documented as a breaking change. Otherwise existing code bases break without warning.

    It appears that the code responsible for this is BasicDeserializerFactory._addExplicitAnyCreator which was newly added in 2.9.2, but I am not certain.

    Should I wait for a fix, or switch to using new ParameterNamesModule(JsonCreator.Mode.PROPERTIES)? Can that have any side effects that I should watch out for?

    Thanks.

    • Sonny
    opened by sonnygill 16
  • code to handle FasterXML/jackson-databind/issues/2141

    code to handle FasterXML/jackson-databind/issues/2141

    Guards against numbers causing CPU or OOM issues when deserializing large numbers into Instant or Duration (see https://github.com/FasterXML/jackson-databind/issues/2141 ) by either:

    • Scientific notation too large (eg 10000e100000)
    • Raw string repesenting a number of length too long
    opened by abracadv8 15
  • Deserializing ZonedDateTime with basic TZ offset notation (0000)

    Deserializing ZonedDateTime with basic TZ offset notation (0000)

    I stumbled upon deserialization issues when the given string equals to ISO8601 "basic" format.

    2019-08-22T12:36:46.361+0000

    Exception message:

    com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.ZonedDateTime` from String "2019-08-22T12:36:46.361+0000": Failed to deserialize java.time.ZonedDateTime: (java.time.format.DateTimeParseException) Text '2019-08-22T12:36:46.361+0000' could not be parsed at index 23
     at [Source: (String)""2019-08-22T12:36:46.361+0000""; line: 1, column: 1]
    

    This format misses out a colon in offset in comparison to extended format (as described at: https://bugs.openjdk.java.net/browse/JDK-8176547).

    2019-08-22T12:36:46.361+00:00

    The issue states that java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME does not support this basic format.

    However, as this format is provided by Spring exception handler I was wondering if there exists any workaround to successfully parse this format. Maybe I am simply missing the obvious here?

    As current workaround I had to register a custom InstantDeserializer and set com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.replaceZeroOffsetAsZ to true. Any hint is very much appreciated.

    Reproduce

    		ZonedDateTime zdt1 = ZonedDateTime.now();
    		ObjectMapper mapper = new ObjectMapper()                                         
                                            .registerModule(new ParameterNamesModule())
    					.registerModule(new Jdk8Module())
                                            .registerModule(new JavaTimeModule());
    
    		//happy path: serialize and deserialize
    		String zdt1S = mapper.writeValueAsString(zdt1);
    		ZonedDateTime zdt1R = mapper.readValue(zdt1S, ZonedDateTime.class);
    		assertThat(zdt1R, notNullValue());
    		gLogger.info("zdt1S: " + zdt1R);
    
    		//ZonedDateTime with basic formatted offset leads to exception
    		String basicTS = "\"2019-08-22T12:36:46.361+0000\"";
    		ZonedDateTime zdt2 = mapper.readValue(basicTS, ZonedDateTime.class);
    		gLogger.info("zdt2S: " + zdt2);
    
    opened by noway1979 13
  • Support use of

    Support use of "pattern" (`ChronoUnit`) for `DurationSerializer` too

    (see #184 for background)

    So, we should support use of @JsonFormat(pattern = "HOURS") (and "config override" equivalent) for writing too. As discussed on #184, there is gnarly challenge with backwards-compatibility -- change should only affect explicit pattern-using case.

    Another challenge is the possibility of fractional values: given Duration may not be exactly divisible by desired unit: for example, we might have "90 seconds" as value, but configured to use "minutes", resulting in 1.5 (ideally). If it is possible to calculate this accurately (enough), I'd like to do that, but I am not sure it is.

    If not, I suggest that only one specific case of fractions would be supported: that of "SECONDS" as explicit unit. It would be output as fractions as needed, based on underlying seconds/nanoseconds values.

    2.12 hacktoberfest 
    opened by cowtowncoder 12
  • Unexpected ValueInstantiationException if register ParameterNamesModule

    Unexpected ValueInstantiationException if register ParameterNamesModule

    I'm deserialize COSE key, every thing works fine with version 2.10.1, but failed with ParameterNamesModule registered since I want object immutable, I'm not sure this is same as https://github.com/FasterXML/jackson-modules-java8/issues/123. here is simple test project pname.zip

    test-needed 
    opened by quaff 12
  • (datatypes) Add Serialization Support for Streams

    (datatypes) Add Serialization Support for Streams

    (moved from earlier issue filed by @jmax01)

    Here is a first pass at serializing Streams. It works for 2.6.x and above. A 2.8.1 version is also shown.

    
    import java.io.IOException;
    import java.lang.reflect.Type;
    import java.util.Iterator;
    import java.util.stream.Stream;
    
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.AnnotationIntrospector;
    import com.fasterxml.jackson.databind.BeanDescription;
    import com.fasterxml.jackson.databind.BeanProperty;
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.MapperFeature;
    import com.fasterxml.jackson.databind.SerializationConfig;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
    import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
    import com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase;
    import com.fasterxml.jackson.databind.type.CollectionLikeType;
    import com.fasterxml.jackson.databind.type.TypeBindings;
    import com.fasterxml.jackson.databind.type.TypeFactory;
    import com.fasterxml.jackson.databind.type.TypeModifier;
    
    /**
     * The Class StreamModule.
     *
     * @author jmaxwell
     */
    public class StreamModule extends SimpleModule {
    
        /** The Constant serialVersionUID. */
        private static final long serialVersionUID = -1324033833221219001L;
    
        @Override
        public void setupModule(final SetupContext context) {
            context.addTypeModifier(new StreamTypeModifier());
            context.addSerializers(new StreamSerializers());
        }
    
        /**
         * The Class StreamTypeModifier.
         */
        public static final class StreamTypeModifier extends TypeModifier {
    
            /**
             * Tested for both 2.6.x and 2.8.1
             */
            @Override
            public JavaType modifyType(final JavaType type, final Type jdkType, final TypeBindings context,
                    final TypeFactory typeFactory) {
    
                if (type.isReferenceType() || type.isContainerType()) {
                    return type;
                }
    
                final Class<?> raw = type.getRawClass();
    
                if (Stream.class.isAssignableFrom(raw)) {
    
                    final JavaType[] params = typeFactory.findTypeParameters(type, Stream.class);
    
                    if (params == null || params.length == 0) {
    
                        return typeFactory.constructReferenceType(raw, TypeFactory.unknownType());
                    }
    
                    return typeFactory.constructCollectionLikeType(raw, params[0]);
                }
                return type;
            }
    
            //
            // the 2.8.1 and above way
            // @Override
            // public JavaType modifyType(JavaType type, Type jdkType, TypeBindings context, TypeFactory typeFactory) {
            //
            // if (type.isReferenceType() || type.isContainerType()) {
            // return type;
            // }
            //
            // Class<?> raw = type.getRawClass();
            //
            // if (Stream.class.isAssignableFrom(raw)) {
            //
            // JavaType[] params = typeFactory.findTypeParameters(type, Stream.class);
            //
            // if (params == null || params.length == 0) {
            //
            // return ReferenceType.upgradeFrom(type, type.containedTypeOrUnknown(0));
            // }
            //
            // return typeFactory.constructCollectionLikeType(raw, params[0]);
            //
            // }
            // return type;
            // }
            //
    
        }
    
        /**
         * The Class StreamSerializers.
         */
        public static final class StreamSerializers extends com.fasterxml.jackson.databind.ser.Serializers.Base {
    
            @Override
            public JsonSerializer<?> findCollectionLikeSerializer(final SerializationConfig config,
                    final CollectionLikeType type, final BeanDescription beanDesc,
                    final TypeSerializer elementTypeSerializer, final JsonSerializer<Object> elementValueSerializer) {
    
                final Class<?> raw = type.getRawClass();
    
                if (Stream.class.isAssignableFrom(raw)) {
    
                    final TypeFactory typeFactory = config.getTypeFactory();
    
                    final JavaType[] params = typeFactory.findTypeParameters(type, Stream.class);
    
                    final JavaType vt = (params == null || params.length != 1) ? TypeFactory.unknownType() : params[0];
    
                    return new StreamSerializer(type.getContentType(), usesStaticTyping(config, beanDesc, null),
                            BeanSerializerFactory.instance.createTypeSerializer(config, vt));
                }
    
                return null;
            }
    
            /**
             * Uses static typing. Copied from {@link BasicSerializerFactory}
             *
             * @param config the config
             * @param beanDesc the bean desc
             * @param typeSer the type ser
             * @return true, if successful
             */
            private static final boolean usesStaticTyping(final SerializationConfig config, final BeanDescription beanDesc,
                    final TypeSerializer typeSer) {
                /*
                 * 16-Aug-2010, tatu: If there is a (value) type serializer, we can not force
                 * static typing; that would make it impossible to handle expected subtypes
                 */
                if (typeSer != null) {
                    return false;
                }
                final AnnotationIntrospector intr = config.getAnnotationIntrospector();
                final JsonSerialize.Typing t = intr.findSerializationTyping(beanDesc.getClassInfo());
                if (t != null && t != JsonSerialize.Typing.DEFAULT_TYPING) {
                    return (t == JsonSerialize.Typing.STATIC);
                }
                return config.isEnabled(MapperFeature.USE_STATIC_TYPING);
            }
    
            /**
             * The Class StreamSerializer.
             */
            public static final class StreamSerializer extends AsArraySerializerBase<Stream<?>> {
    
                /** The Constant serialVersionUID. */
                private static final long serialVersionUID = -455534622397905995L;
    
                /**
                 * Instantiates a new stream serializer.
                 *
                 * @param elemType the elem type
                 * @param staticTyping the static typing
                 * @param vts the vts
                 */
                public StreamSerializer(final JavaType elemType, final boolean staticTyping, final TypeSerializer vts) {
                    super(Stream.class, elemType, staticTyping, vts, null);
                }
    
                /**
                 * Instantiates a new stream serializer.
                 *
                 * @param src the src
                 * @param property the property
                 * @param vts the vts
                 * @param valueSerializer the value serializer
                 */
                public StreamSerializer(final StreamSerializer src, final BeanProperty property, final TypeSerializer vts,
                        final JsonSerializer<?> valueSerializer) {
                    super(src, property, vts, valueSerializer, false);
                }
    
                @Override
                public void serialize(final Stream<?> value, final JsonGenerator gen, final SerializerProvider provider)
                        throws IOException {
                    this.serializeContents(value, gen, provider);
                }
    
                /**
                 * withResolved.
                 *
                 * @param property the property
                 * @param vts the vts
                 * @param elementSerializer the element serializer
                 * @param unwrapSingle ignored always false since streams are one time use I don't believe we can get a
                 *            single element
                 * @return the as array serializer base
                 */
                @Override
                public StreamSerializer withResolved(final BeanProperty property, final TypeSerializer vts,
                        final JsonSerializer<?> elementSerializer, final Boolean unwrapSingle) {
                    return new StreamSerializer(this, property, vts, elementSerializer);
                }
    
                @Override
                protected void serializeContents(final Stream<?> value, final JsonGenerator gen,
                        final SerializerProvider provider) throws IOException {
    
                    provider.findValueSerializer(Iterator.class, null)
                        .serialize(value.iterator(), gen, provider);
    
                }
    
                @Override
                public boolean hasSingleElement(final Stream<?> value) {
                    // no really good way to determine (without consuming stream), so:
                    return false;
                }
    
                @Override
                protected StreamSerializer _withValueTypeSerializer(final TypeSerializer vts) {
    
                    return new StreamSerializer(this, this._property, vts, this._elementSerializer);
                }
            }
        }
    }
    

    Tests:

    
    import static org.junit.Assert.*;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    import java.util.stream.Stream;
    
    import org.junit.Test;
    
    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.datatype.guava.GuavaModule;
    import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
    import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
    import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
    import com.fasterxml.jackson.module.mrbean.MrBeanModule;
    import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
    import com.theice.cds.common.serialization.json.jackson2.StreamModule;
    
    @SuppressWarnings("javadoc")
    public class StreamModuleTest {
    
        public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new GuavaModule())
            .registerModule(new Jdk8Module())
            .registerModule(new JavaTimeModule())
            .registerModule(new ParameterNamesModule())
            .registerModule(new AfterburnerModule())
            .registerModule(new StreamModule())
            .registerModule(new MrBeanModule());
    
        static <T> void assertRoundTrip(final Collection<T> original, final ObjectMapper objectMapper) throws IOException {
    
            final Stream<T> asStream = original.stream();
    
            final String asJsonString = objectMapper.writeValueAsString(asStream);
    
            System.out.println("original: " + original + " -> " + asJsonString);
    
            final Collection<T> fromJsonString = OBJECT_MAPPER.readValue(asJsonString,
                    new TypeReference<Collection<T>>() {});
    
            assertEquals(original, fromJsonString);
        }
    
        @SuppressWarnings("deprecation")
        @Test
        public void testEmptyStream() throws IOException {
    
            assertRoundTrip(new ArrayList<>(), OBJECT_MAPPER.copy()
                .enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
    
            // shouldn't this fail?
            assertRoundTrip(new ArrayList<>(), OBJECT_MAPPER.copy()
                .disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)
                .disable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
        }
    
        @Test
        public void testSingleElementStream() throws IOException {
    
            final List<String> collection = new ArrayList<>();
            collection.add("element1");
    
            assertRoundTrip(collection, OBJECT_MAPPER.copy()
                .enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
                .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
    
            assertRoundTrip(collection, OBJECT_MAPPER.copy()
                .disable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
                .disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
    
            // should fail but can't for stream
            assertRoundTrip(collection, OBJECT_MAPPER.copy()
                .enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
                .disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
        }
    
        @Test
        public void testMultipleElementStream() throws IOException {
    
            final List<String> collection = new ArrayList<>();
            collection.add("element1");
            collection.add("element2");
    
            assertRoundTrip(collection, OBJECT_MAPPER);
    
        }
    }
    
    opened by cowtowncoder 11
  • custom deserialization with absent Optional returns null

    custom deserialization with absent Optional returns null

    When using a custom deserialization for an Optional field, then the absence of that field is returning null instead of Optional.empty().

    I reproduced with my own test method in the test class OptionalTest.java:

        public void testWithCustomDeserializerIfOptionalAbsent() throws Exception
        {
            assertEquals(Optional.empty(), MAPPER.readValue("{}",
                    CaseChangingStringWrapper.class).value);
    
            assertEquals(Optional.empty(), MAPPER.readValue(aposToQuotes("{'value':null}"),
                    CaseChangingStringWrapper.class).value);
        }
    

    The first check fails, because of Jackson setting the value field to null.

    opened by rkeytacked 11
  • Should not parse `LocalDate`s from number (timestamp) -- or at least should have an option preventing

    Should not parse `LocalDate`s from number (timestamp) -- or at least should have an option preventing

    In Jackson 2.8.x I had a unit test that parsed the following and expected an exception.

    {
      "date": 123,
      "message": "Happy New Year!"
    }
    

    As of 2.9 related to this commit https://github.com/FasterXML/jackson-modules-java8/pull/22/files#diff-a1552641fab3c9690f2c8b0f5ee2fe89R102 it now parses as 1970-05-04

    Is there a way to turn this off if I expect my dates to come in as a different format? I don't want to parse random integers as dates when it should be invalid input.

    2.11 
    opened by billoneil 11
  • InstantDeserializer is not respecting leniency w.r.t. to numeric values

    InstantDeserializer is not respecting leniency w.r.t. to numeric values

    We annotated some constructor properties with @JsonFormat(lenient = OptBoolean.FALSE) but found that the InstantDeserializer is not applying it when deserializing numeric values.

    Expected _failForNotLenient(parser, context, JsonToken.VALUE_STRING) to be invoked for the numeric cases in InstantDeserialize#deserialize when not lenient.

    (It looks like LocalDateDeserializer implementation is applying leniency in the case of an integer value as expected; InstantDeserializer should implement similarly.)

    opened by wong-git 5
  • Parses ISO 8601 timestamp that ends with Z into LocalDateTime

    Parses ISO 8601 timestamp that ends with Z into LocalDateTime

    Jackson does not raise an error when parsing JSON containing a date time ending with "Z" like 1970-01-01T12:00:00Z into LocalDateTime. According to the documentation of DateTimeFormatter.ISO_LOCAL_DATE_TIME it should only parse date times without "Z" or any other offset.

    opened by saibotma 3
  • LocalDateTimeDeserializer should use SystemDefault timezone

    LocalDateTimeDeserializer should use SystemDefault timezone

     if (_formatter == DEFAULT_FORMATTER) {
    	                // JavaScript by default includes time and zone in JSON serialized Dates (UTC/ISO instant format).
    	                if (string.length() > 10 && string.charAt(10) == 'T') {
    	                   if (string.endsWith("Z")) {
    	                       return LocalDateTime.ofInstant(Instant.parse(string), ZoneOffset.UTC);
    	                   } ...
    

    When deserializer a json date to LocalDateTime, we always want to get a date with default time zone or custom timezone. Is there a way to special the timezone?

    opened by languanghao 4
  • Discrepancy in deserialization of ZonedDateTime

    Discrepancy in deserialization of ZonedDateTime

    Given input string: 2021-02-01T19:49:04.0513486Z

    Jackson will deserialize a ZonedDateTime with the following properties:

    • offset: "Z" (type ZonedOffset)
    • zone: "UTC" (type ZoneRegion)

    Using the standard call

    DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(inputString, ZonedDateTime::from);
    

    produces:

    • offset: "Z" (type ZonedOffset)
    • zone: "Z" (type ZonedOffset)

    This causes an issue in comparisons because id value "UTC" != id value "Z". This also produces a redundancy in the toString of the ZonedDateTime because it detects that the offset != zone.

    toString for Jackson result: 2021-02-01T19:49:04.051348600Z[UTC] toString for standard result: 2021-02-01T19:49:04.051348600Z

    The latter can be consumed without extra processing and is the desired result.

    Would it be possible to update the deserializer to behave like the standard call?

    opened by dscalzi 1
  • jackson-module-parameter-names: @JsonPropertyName still required if @JsonDeserialize is used

    jackson-module-parameter-names: @JsonPropertyName still required if @JsonDeserialize is used

    If ParameterNamesModule is registered, { "a": 1, "b": 1 } can be deserialized to an instance of:

    public class Foo{
         public Foo(int a, int b) { }
    }
    

    but not to an instance of:

    public class Foo{
        public Foo(@JsonDeserialize(converter=Incr.class) int a, int b) { }
    }
    

    unless you declare the property name again:

    public class Foo{
         public Foo(@JsonDeserialize(converter=Incr.class)@JsonProperty("a") int a, int b) { }
    }
    
    2.12 hacktoberfest 
    opened by clo-vis 4
  • DeserializationFeature ALLOW_DATES_AS_TIMESTAMPS

    DeserializationFeature ALLOW_DATES_AS_TIMESTAMPS

    With SerializationFeature.WRITE_DATES_AS_TIMESTAMPS it's possible to output date and time data as ISO 8601, but there seems to be no way to only accept date and time data in ISO 8601 format.

    Though I'd suggest to add some kind of DeserializationFeature.ALLOW_DATES_AS_TIMESTAMPS or MapperFeature.ALLOW_COERCION_OF_DATES which disables the coercion of date and time data.

    In other words: {"myInstant": 123456789} should throw an exception, and only {"myInstant": "1973-11-29T21:33:09Z"} should be allowed.

    Or is there already an setting that I missed?

    date-time-config 
    opened by benneq 4
  • ZonedDateTime documentation seems to be incorrect

    ZonedDateTime documentation seems to be incorrect

    It says: https://github.com/FasterXML/jackson-modules-java8/tree/master/datetime

    For string representation default formats for deserialization are: | Java type | Format | |------------|---------| |ZonedDateTime|DateTimeFormatter.ISO_ZONED_DATE_TIME|

    But in the code I see: https://github.com/FasterXML/jackson-modules-java8/blob/1534c557d4ea997b7a80a9bcba4c98cd558e4493/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerializer.java#L22-L23

    documentation hacktoberfest 
    opened by AceHack 1
  • InstantSerializer doesn't respect any format-related settings without replacing serializer instance

    InstantSerializer doesn't respect any format-related settings without replacing serializer instance

    InstantSerializer doesn't respect any of the following standard Jackson configuration settings:

    ObjectMapper#setDateFormat @JsonFormat(pattern) on an Instant others?

    Instead, I have to create and manually override the default InstantSerializer registered by the module.

    This isn't well documented and is definitely confusing when using the library. Can we make InstantSerializer actually respect date-related or pattern-related options from the core library?

    Looking at InstantSerializerBase, the provided context has the format from the mapper, but doesn't use it. The formatters attached to the serializer directly are both null by default.

    public void serialize(T value, JsonGenerator generator, SerializerProvider provider) throws IOException {
            if (this.useTimestamp(provider)) {
                if (this.useNanoseconds(provider)) {
                    generator.writeNumber(DecimalUtils.toBigDecimal(this.getEpochSeconds.applyAsLong(value), this.getNanoseconds.applyAsInt(value)));
                } else {
                    generator.writeNumber(this.getEpochMillis.applyAsLong(value));
                }
            } else {
                String str;
                if (this._formatter != null) {
                    str = this._formatter.format(value);
                } else if (this.defaultFormat != null) {
                    str = this.defaultFormat.format(value);
                } else {
                    str = value.toString();
                }
    
                generator.writeString(str);
            }
        }
    

    So the SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is respected but that's it.

    Does it make sense to use the provider's defaultSerializeDateValue(long timestamp, JsonGenerator gen) method here and pass the timestamp from the Instant?

    2.12 good first issue hacktoberfest json-format 
    opened by TomRK1089 9
  • java9+ OffsetDateTimeSerializer/OffsetDateTimeDeserializer miliseconds instead of nano config

    java9+ OffsetDateTimeSerializer/OffsetDateTimeDeserializer miliseconds instead of nano config

    Hi, we have set SerializationFeature.WRITE_DATES_AS_TIMESTAMPS to false and in java8 it wrote time in milliseconds precision (java worked this way for a long time).
    After migrating to java11 the precision is nanoseconds

    • (Java 8- - 2020-01-28T16:31:37.698+01:00)
    • (Java 9+ - 2020-01-28T16:31:37.698185+01:00)

    This is fine, but unfortunately the services both northbound and southbound of our system can work with milliseconds (3 digits). Would it be possible to create a SerializationFeature to enable java8 behavior meaning serializing only to milliseconds precision? We use a spring boot autoconfiguration where we would like to set this globally for all of our microservices.

    Thanks, Ondrej

    PS: Maybe until then, could you provide us instructions for the best way to setup this in Spring Boot 2.2.x? Thanks

    2.12 
    opened by drej1 5
  • Consider performance improvements for Date/Time parsing

    Consider performance improvements for Date/Time parsing

    Jsoniter project (https://github.com/plokhotnyuk/jsoniter-scala) has many impressive performance optimizations; linked f.ex from here:

    https://www.reddit.com/r/java/comments/darehu/jackson_release_210/f1ysf1e/

    and ones relevant to this repo have to with Java 8 date/time type decoding. It would be great to investigate if some of the techniques could be used here: handling of Date/Time values is notoriously CPU intensive (although not necessarily low-level encoding/decoding?).

    opened by cowtowncoder 0
Owner
FasterXML, LLC
FasterXML, LLC
Set of support modules for Java 8 datatypes (Optionals, date/time) and features (parameter names)

Overview This is a multi-module umbrella project for Jackson modules needed to support Java 8 features, especially with Jackson 2.x that only requires

FasterXML, LLC 317 Apr 30, 2021
A modern JSON library for Kotlin and Java.

Moshi Moshi is a modern JSON library for Android and Java. It makes it easy to parse JSON into Java objects: String json = ...; Moshi moshi = new Mos

Square 7.1k Mar 13, 2021
High performance JVM JSON library

DSL-JSON library Fastest JVM (Java/Android/Scala/Kotlin) JSON library with advanced compile-time databinding support. Compatible with DSL Platform. Ja

New Generation Software Ltd 704 Mar 11, 2021
Compare JSON in your Unit Tests

JsonUnit JsonUnit is a library that simplifies JSON comparison in tests. APIs AssertJ integration Hamcrest matchers Spring MVC assertions Spring WebTe

Lukáš Křečan 529 Mar 12, 2021
Extension module to properly support datatypes of javax.money

Jackson Datatype Money Jackson Datatype Money is a Jackson module to support JSON serialization and deserialization of JavaMoney data types. It fills

Zalando SE 164 Mar 12, 2021
MapNeat is a JVM library written in Kotlin that provides an easy to use DSL (Domain Specific Language) for transforming JSON to JSON, XML to JSON, POJO to JSON in a declarative way.

MapNeat is a JVM library written in Kotlin that provides an easy to use DSL (Domain Specific Language) for transforming JSON to JSON, XML to JSON, POJ

Andrei Ciobanu 17 Mar 4, 2021
Java JsonPath implementation

Jayway JsonPath A Java DSL for reading JSON documents. Jayway JsonPath is a Java port of Stefan Goessner JsonPath implementation. News 10 Dec 2020 - R

null 5.7k Mar 13, 2021
Java JsonPath implementation

Jayway JsonPath A Java DSL for reading JSON documents. Jayway JsonPath is a Java port of Stefan Goessner JsonPath implementation. News 10 Dec 2020 - R

null 5.7k Mar 15, 2021
Genson a fast & modular Java <> Json library

Genson Genson is a complete json <-> java conversion library, providing full databinding, streaming and much more. Gensons main strengths? Easy to use

null 197 Feb 26, 2021
A Java annotation processor used for automatically generating better builder codes.

BetterBuilder BetterBuilder is a Java annotation processor used for automatically generating better builder codes(builder design pattern), which can m

LEO D PEN 10 Mar 8, 2021
Vert.x jOOQ DSL

jOOQ.x - Vertx jOOQ DSL jooqx leverages the power of typesafe SQL from jOOQ DSL and running on SQL connection in a reactive and non-blocking of SQL dr

zero88 12 Mar 30, 2021
Core part of Jackson that defines Streaming API as well as basic shared abstractions

Overview This project contains core low-level incremental ("streaming") parser and generator abstractions used by Jackson Data Processor. It also incl

FasterXML, LLC 1.9k Mar 13, 2021
A JSON Schema validation implementation in pure Java, which aims for correctness and performance, in that order

Read me first The current version of this project is licensed under both LGPLv3 (or later) and ASL 2.0. The old version (2.0.x) was licensed under LGP

Java Json Tools 1.4k Mar 14, 2021
Lean JSON Library for Java, with a compact, elegant API.

mJson is an extremely lightweight Java JSON library with a very concise API. The source code is a single Java file. The license is Apache 2.0. Because

Borislav Iordanov 69 Apr 15, 2021