Code Monkey home page Code Monkey logo

jackson-modules-java8's Introduction

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:

<!--	Parameter names	-->
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
</dependency>

<!--	Java 8 Date/time	-->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

<!--	Java 8 Datatypes	-->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</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 (beamerblvd@github) 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 (kupci@github) is the current maintainer of Java 8 date/time module

More

See Wiki for more information (javadocs).

jackson-modules-java8's People

Contributors

angelyan avatar caluml avatar cowtowncoder avatar etrandafir93 avatar jbouyoud avatar joohyukkim avatar jpmoresmau avatar kaseifr avatar kekbur avatar kevinjom avatar kupci avatar lpandzic avatar maciejdobrowolski avatar maciekdeb avatar mhicauber avatar mpkorstanje avatar mydata avatar obarcelonap avatar pjfanning avatar raman-babich avatar recklyss avatar reftel avatar remal avatar rkeytacked avatar samwill avatar semen-levin-epam avatar stefanbirkner avatar toddjonker avatar vetler avatar vmeunier avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jackson-modules-java8's Issues

Typo on Wiki Homepage

Group id: com.fasterxml.jackson.datatyep should be com.fasterxml.jackson.datatype

Include.NON_NULL does not work on Java 8 Optional

Following is a minimal reproduction of the issue. In short, I would have expected that, when using the Jdk8Module, empty Optionals wouldn't be serialized when using the Include.NON_NULL serialization inclusion.

public class Issue {

    public static void main(String[] args) throws Exception {
        Model model = new Model();
        model.setId(OptionalLong.empty());
        model.setOrder(null);
        model.setName(Optional.ofNullable(null));
        model.setType(null);

        ObjectMapper mapper = new ObjectMapper()
            .registerModule(new Jdk8Module())
            .setSerializationInclusion(NON_NULL);

        mapper.writeValue(System.out, model); // prints {"id":null,"name":null}
    }

    static class Model {
        private OptionalLong id;
        private Long order;
        private Optional<String> name;
        private String type;

        public OptionalLong getId() {
            return id;
        }

        public void setId(OptionalLong id) {
            this.id = id;
        }

        public Long getOrder() {
            return order;
        }

        public void setOrder(Long order) {
            this.order = order;
        }

        public Optional<String> getName() {
            return name;
        }

        public void setName(Optional<String> name) {
            this.name = name;
        }
        
        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }
    }

}

Missing type ID in java.util.Optional return value in Spring REST controller method

Please see my Stackoverflow question. I have made a simple example demonstrating the problem (Spring boot 2, gradle build, call test task). When using Optional as return value of Spring REST controller method then the type ID is not included in the Json response and the client complains about that:

Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'

Version is 2.9.4. Maybe this is not an issue with the jdk8 module but with Spring REST, I am not sure.

(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);

    }
}

`InstantDeserializer` is not working with offset of zero `+00:00` and `+00`

I am working on a project with Spring boot, and it uses Jackson as the json parse library behind to scene to parse http input message into Objects. And some of our clients will pass a timestamp string like this 2017-03-13T11:11:11.000+00:00, and I found that didn't work with current implementation of InstantDeserializer.java.

I am thinking if we can replace a zone offset of zero that is +0000 with Z, we probably should also replace +00:00 and +00 as well, because as I found in this Wiki page, they are also valid zero offsets.

I didn't find a CONTRIBUTION guide so I've made a quick change here, I am not sure if you guys are happy with that. If so, I'd like to make a request to fix it.

Allow `@JsonFormat.timezone` use for serialization but not use on deserialization

Currently JsonFormat.timezone overwrites the timezone used for both deserialization and serialization. I would like a way to allow Instants/DateTimes to be read in correctly as seen, but then allow timezone to be specified for serialization.

Posible solution:

Allow for a new JsonFormat.fallbackTimezone. This would not override ZonedDateTime timezone on serialization AND it would not overwrite the provided timezone on deserialization. But it would provide one if needed (eg: Instant serialization, or deserialization from a pattern with no TZ or optional TZ that is not provided). This is somewhat more involved as a pattern that has an optional timezone yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX] would require looking at the provided value to determine if we need to use the fallbackTimezone during deserialization.

My Current workaround just ignores the JsonFormat.timezone in the InstantDeserializer.

Here is an example showing the issue:

public class Foo {
	public static void main(String[] args) {
		try {
			ObjectMapper om = new ObjectMapper();
			JavaTimeModule jtm = new JavaTimeModule();
			//jtm.addDeserializer(Instant.class, FixedDeserializer.INSTANT);
			om.registerModule(new JavaTimeModule());

			// TEST 1 - Read value in with JsonFormat.timezone set
			Bar bar = om.readValue("{\"fiz\":\"2016-09-20T16:37:02-02:00\"}", Bar.class);
			// The Instant is incorrect, it used the @JsonFormat.timezone to overwride what was provided
			System.out.println(bar.fiz);  // 2016-09-20T21:37:02Z
			// Value writes correctly if fiz was set correctly
			System.out.println(om.writeValueAsString(bar)); //{"fiz":"2016-09-20T16:37:02.000-05:00"}


			// TEST 2 - Read value in with JsonFormat.timezone unset
			BarNoTz barNoTz = om.readValue("{\"fiz\":\"2016-09-20T16:37:02-02:00\"}", BarNoTz.class);
			// The Instant is correct :)
			System.out.println(barNoTz.fiz); // 2016-09-20T18:37:02Z
			// Value cannot write, because it does not know what timezone to write in.  Throws UnsupportedField: YearOfEra
			//System.out.println(om.writeValueAsString(barNoTz)); // THROWN UnsupportedTemporalTypeException


		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static class Bar {

		@JsonFormat(
				shape = JsonFormat.Shape.STRING,
				pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX",
				timezone = "America/Chicago"
		)
		private Instant fiz;

		public Instant getFiz() {
			return fiz;
		}

		public void setFiz(Instant fiz) {
			this.fiz = fiz;
		}
	}
        public static class BarNoTz {

		@JsonFormat(
				shape = JsonFormat.Shape.STRING,
				pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX"
		)
		private Instant fiz;

		public Instant getFiz() {
			return fiz;
		}

		public void setFiz(Instant fiz) {
			this.fiz = fiz;
		}
	}
}

My current workaround:

public class FixedDeserializer<T extends Temporal> extends InstantDeserializer<T> {
		public static final InstantDeserializer<Instant> INSTANT = new FixedDeserializer<>(
				Instant.class, DateTimeFormatter.ISO_INSTANT,
				Instant::from,
				a -> Instant.ofEpochMilli(a.value),
				a -> Instant.ofEpochSecond(a.integer, a.fraction),
				null,
				true // yes, replace +0000 with Z
		);

		public FixedDeserializer(Class<T> supportedType, DateTimeFormatter formatter, Function<TemporalAccessor, T> parsedToValue,
				Function<FromIntegerArguments, T> fromMilliseconds, Function<FromDecimalArguments, T> fromNanoseconds,
				BiFunction<T, ZoneId, T> adjust, boolean replace0000AsZ) {
			super(supportedType, formatter, parsedToValue, fromMilliseconds, fromNanoseconds, adjust, replace0000AsZ);
		}

		public FixedDeserializer(InstantDeserializer<T> base, DateTimeFormatter f) {
			super(base, f);
		}

		public FixedDeserializer(InstantDeserializer<T> base, Boolean adjustToContextTimezoneOverride) {
			super(base, adjustToContextTimezoneOverride);
		}


		@SuppressWarnings("unchecked")
		@Override
		public JsonDeserializer<T> createContextual(DeserializationContext ctxt,
				BeanProperty property) throws JsonMappingException
		{
			JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
			if (format != null) {
				if (format.hasPattern()) {
					final String pattern = format.getPattern();
					final Locale locale = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
					DateTimeFormatter df;
					if (locale == null) {
						df = DateTimeFormatter.ofPattern(pattern);
					} else {
						df = DateTimeFormatter.ofPattern(pattern, locale);
					}
					//FIXED to not allow overrides as I always use patterns that has timezones.
//					if (format.hasTimeZone()) {
//						df = df.withZone(format.getTimeZone().toZoneId());
//					}
					return withDateFormat(df);
				}
				// any use for TimeZone?
			}
			return this;
		}
	}

NON_ABSENT works for Optional<Long> but not OptionalLong

When I use NON_ABSENT for a field that is OptionalLong.empty(), I get this:

Caused by: java.util.NoSuchElementException: No value present
	at java.util.OptionalLong.getAsLong(OptionalLong.java:118)
	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.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:687)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)

With Optional<Long>, a value that is empty() is skipped as expected.

LocalDateTime treated as mandatory @QueryParam

According to http://stackoverflow.com/a/32363522/14731 @QueryParam is supposed to be optional. If the parameter is missing, Jersey is supposed to set the argument to null.

Something seems to be wrong with the LocalDateTime handler though, because:

@GET
public Response get(@QueryParam("time") LocalDateTime time)

never matches if the query parameter is missing, but:

@GET
public Response get(@QueryParam("time") String time)

works just fine. Meaning, String is treated as optional but LocalDateTime is not.

I tried adding breakpoints to the deserializer but it doesn't seem to even get hit. Still, I suspect this is a Jackson-specific bug. Can you please take a look?

DateTime parsing error

Hi,

We're currently using the version 2.8.6 of jackson-datatype-jsr310 and we got a bad issue in one of our production environment...
When our JSON contains the date: '2017-07-25T20:22:58.8Z', Jackson throw the following exception:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.time.OffsetDateTime from String "2017-07-25T20:22:58.8Z": Text '2017-07-25T20:22:58.8Z' could not be parsed at index 20
 at [Source: java.io.BufferedInputStream@4248ed58; line: 38, column: 17] (through reference chain: io.barracks.membergateway.model.Device["firstSeen"])

	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:74)
	at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1410)
	at com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase._rethrowDateTimeException(JSR310DeserializerBase.java:79)
	at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:208)
	at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:48)
	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:511)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:396)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1198)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:314)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2922)
	at org.springframework.boot.test.json.JacksonTester.readObject(JacksonTester.java:87)
	at org.springframework.boot.test.json.AbstractJsonMarshalTester.read(AbstractJsonMarshalTester.java:267)
	at org.springframework.boot.test.json.AbstractJsonMarshalTester.read(AbstractJsonMarshalTester.java:243)
	at org.springframework.boot.test.json.AbstractJsonMarshalTester.readObject(AbstractJsonMarshalTester.java:231)
	at io.barracks.membergateway.model.json.DeviceJsonTest.deserialize_shouldReturnCompleteObject(DeviceJsonTest.java:60)
	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.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
	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.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.time.format.DateTimeParseException: Text '2017-07-25T20:22:58.8Z' could not be parsed at index 20
	at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
	at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1777)
	at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:202)
	... 42 more

If I run OffsetDateTime.parse("2017-07-25T20:22:58.8Z"), the parsing works correctly. So it seems to happen only during deserialization with Jackson-datatype.

We never had that issue until today. It seems like the bug happens only if the part before the Z is composed of a single number.

Add a way to customize what happens when Optional is empty

I have this simple use case using Spring Boot serializing a java.util.Optional.

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Optional<User> getUser(@PathVariable("id") String id) {
    return queryUser.execute(id);        
}

It works fine when there is a value, but when the value is empty, it serializes it to null. I'd like to have a way to customize what happens when the value is empty, maybe passing a callback when creating the Jdk8Module instance.

For my current use case, I 'd like to throw an exception that represents a 404 error, but maybe in other applications it would be necessary to use another object instance representing the missing data.

Java8 date has no milliseconds long transform to `LocalDateTime`

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.

JsonTypeInfo and Optional

I think i have found a bug, while upgrading my project from 2.8.7 -> 2.8.8. The classname is no longer part of the serialized json string. The test testWithOptional below fails in 2.8.8:

import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;

public class Test {
    ObjectMapper mapper = new ObjectMapper().registerModule(new Jdk8Module());

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
    @JsonSubTypes({@JsonSubTypes.Type(name = "Container", value = ChildClass.class)})
    public static class Base {
    }

    public static class ChildClass extends Base {
    }

    @org.junit.Test
    public void testWithOptional() throws IOException {
        String json = mapper.writeValueAsString(Optional.of(new ChildClass()));
        // breaks in com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.8.8 and above
        assertTrue(json, json.contains("type"));
    }

    @org.junit.Test
    public void test() throws IOException {
        String json = mapper.writeValueAsString(new ChildClass());
        assertTrue(json, json.contains("type"));
    }
}

LocalDateTime to timestamp serailzation and deserialization

Hi, guys, I'm a little confused. Here is the doc:
LocalDate, LocalTime, LocalDateTime, and OffsetTime, which cannot portably be converted to timestamps and are instead represented as arrays when WRITE_DATES_AS_TIMESTAMPS is enabled.

  • LocalDateTime doesn't have a TZ information, it's the local time (the place of the object creation)
  • The timestamp is a long and it doesn't have a TZ information it's the local time since 1970 Jan 1...
    (the place of the timestamp creation).

So where is the problem?

Let reuse of Instant serializer provided by JavaTimeModule

I'd like ObjectMapper write most Temporals as Strings (thus disabling WRITE_DATES_AS_TIMESTAMPS), but for Instants I'd like to use timestamps.
JSR310FormattedSerializerBase has the ability to override timestamp mode, but it is controlled by Shape annotation only.
Can we have a constructor for InstantSerializer.java (and maybe other descendants of JSR310FormattedSerializerBase?) that allows setting _useTimestamp?

ParameterNamesModule + JsonCreator + inheritance cause deserialization errors

Running

public class FooTest {

    public static void main(String[] args) throws IOException {
        new ObjectMapper()
                .registerModule(new ParameterNamesModule())
                .readValue("\"foo\"", Foo.class);
    }

    public interface Foo {
        String getFoo();

        @JsonCreator
        static Foo valueOf(String foo) {
            return new FooImpl(foo);
        }
    }

    public static class FooImpl implements Foo {
        private final String foo;

        public FooImpl(String foo) {
            this.foo = foo;
        }

        @Override
        public String getFoo() {
            return foo;
        }
    }
}

results in

Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `example.FooTest$Foo` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('foo')
 at [Source: (String)""foo""; line: 1, column: 1]
	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.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:370)
	at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:314)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1351)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:170)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
        at example.FooTest.main(FooTest.java:17)

It works correctly if ParamterNamesModule is removed, or if Foo and FooImpl are combined.

Stream Serializers need to close.

The Stream Serializers do not close on error this can lead to leaks if the value supplier has open resources (streaming from a ResultSet for example).

I have a PR ready,

`YearKeyDeserializer` doesn't work with non-padded year values

Description & proposal

The YearKeyDeserializer is unable to deserialize keys that are not in the format yyyy (e.g. "1"). It expects the year to be padded with zeros ("0001").

In contrast, the YearDeserializer is in fact working correctly and can handle non-padded values as well.

This could probably be fixed by changing the YearKeyDeserializer#deserialize:30 to something like this (we've currently registered a custom deserializer that does this):

@Override
protected Year deserialize(String key, DeserializationContext ctxt) throws IOException {
-    try {
-        return Year.parse(key, FORMATTER);
+        return Year.of(Integer.parseInt(key));
-    } catch (DateTimeException e) {
-        return _rethrowDateTimeException(ctxt, Year.class, e, key);
-    }
}

Steps to reproduce

Here's a simple test to demonstrate the behavior:

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());

@Test
public void serializeAndDeserializeYearKey() throws IOException {
    Map<Year, Float> testMap = Collections.singletonMap(Year.of(1), 1F);

    String serialized = OBJECT_MAPPER.writeValueAsString(testMap);

    TypeReference<Map<Year, Float>> yearFloatTypeReference = new TypeReference<Map<Year, Float>>() {};

    Map<Year, Float> deserialized = OBJECT_MAPPER.readValue(serialized, yearFloatTypeReference);

    assertThat(deserialized).isEqualTo(testMap);
}

The above will throw java.time.format.DateTimeParseException: Text '1' could not be parsed at index 0

(datatypes) OptionalInt + YAMLFactory not working correctly for empty case

(moved from one reported at old jackson-datatype-jdk8, issue 32)


When deserializing a yaml file if my pojo has OptionalInt rather than Optional, the empty case doesn't seem to be handled right.

Test file:

test:
  value:

Test pojo class:

    private static class MyTestPojo {
    private OptionalInt value = OptionalInt.empty();

    public OptionalInt getValue() {
        return value;
    }

    public void setValue(OptionalInt value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "MyTestPojo{" +
                "value=" + value +
                '}';
    }
}

And the driving code:

private static final ObjectMapper om = new ObjectMapper(new YAMLFactory()).registerModule(new Jdk8Module());
private static final Path testPath = Paths.get("C:/Development/testYaml.txt");

private static final TypeReference<Map<String, MyTestPojo>> tr = new TypeReference<Map<String, MyTestPojo>>() {
};

public static void main(String... args) throws IOException {
    try (BufferedReader br = Files.newBufferedReader(testPath, StandardCharsets.UTF_8)) {
        Map<String, Object> read = om.readValue(br, tr);
        System.out.println(read);
    }
}

When this file is deserialized with this code, I get a response of {test=MyTestPojo{value=OptionalInt[0]}} (as in, it's an optionalint that's present with a value of 0). When I use Optional, I get the correct printout of {test=MyTestPojo{value=Optional.empty}} . For some reason OptionalInt doesn't handle the empty case right here. I've tested without the yaml factory on a json file and it seems to work. I am using version 2.6.4 of Jackson.

DateTimeSerializerBase doesn't use WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS with `java.sql.Timestamp`

When converted using DateTimeSerializerBase, java.sql.Timestamp instances are not written in nanoseconds, even if the WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS feature is enabled.

I found this error doing the following:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
mapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.convertValue(new Timestamp(1456412409000), Instant.class)

`DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS` not respected

the following JSON

{"zdt":["2017-03-22T14:50:08.121+01:00"]}

Cannot be deserialized into the following class:

public static class TestJava8TimeBean{
		private ZonedDateTime zdt=ZonedDateTime.now();
		
		public TestJava8TimeBean(){
			
			
		}
		
		public ZonedDateTime getZdt() {
			return zdt;
		}
		
		public void setZdt(ZonedDateTime zdt) {
			this.zdt = zdt;
		}

But the mapper I use defines:

mapper.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true);

So using an array with one value should work. It does with strings. Also probably if DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT is enabled, an empty array should result in a null value.

offsetdatetime deserialization does not handle nano seconds correctly

I reported it at the old FasterXML/jackson-datatype-jsr310, and then I found this repo is the current in maintain workspace, then I also checked the corresponding source, which suggests that the reported issue still exists. It is possible that I missed something in this new repo, but just report it again to make sure it is OK.

the origin issue report:

https://github.com/FasterXML/jackson-datatype-jsr310/issues/96

when InstantDeserializer get a long value for OffsetDateTime, it will entering the following method:

    protected T _fromLong(DeserializationContext context, long timestamp)
    {
        if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
            return fromNanoseconds.apply(new FromDecimalArguments(
                    timestamp, 0, this.getZone(context)
            ));
        }
        return fromMilliseconds.apply(new FromIntegerArguments(
                timestamp, this.getZone(context)));
    }

Notice that, with the default configuration, READ_DATE_TIMESTAMPS_AS_NANOSECONDS is true, it will create a FromDecimalArguments with the passed timestamp value, and which will be passed to the following source:

    public static final InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new InstantDeserializer<>(
            OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
            OffsetDateTime::from,
            a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
            a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
            (d, z) -> d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())),
            true // yes, replace +0000 with Z
    );

to be precision, the following row:

a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),

which will handle the value as epoch seconds, rather than nano second.

Is there anything that I misunderstand? Or it is actually a bug?

Deserialize zone offset with or without colon

Is there a way to configure the module to accept zone offset expressed with and without colon ?

In other words we'd like the parser to read the following two forms:

  • 1970-01-01T01:00:00.000+01:00
  • 1970-01-01T01:00:00.000+0100

Note: it already accepts these two forms but ONLY if offset is zero (+00:00 or +0000)...

NON_ABSENT not working for Optional.empty()

I expect the below code to output {"x":5} but it outputs {"x":5,"z":null}

Map<String, Object> map = new HashMap<>();
map.put("x", 5);
map.put("y", null);
map.put("z", Optional.empty());
String json = new ObjectMapper()
    .registerModule(new Jdk8Module())
    .setDefaultPropertyInclusion(Include.NON_ABSENT)
    .writeValueAsString(map);
System.out.println(json);

NON_EMPTY produces the desired output

`Jdk8Serializer.findReferenceSerializer()` leads to `StackOverflowError` in 2.8.9

The commit 4d71c27 made the Jdk8Serializer.findReferenceSerializer method to go into infinite recursion if the matched reference type is not found.

Before the commit the method returned null, I think the author intended to call super.findReferenceSerializer at the end like the code does in the findSerializer method.

Currently 2.8.9 release is unusable for code that registers Jdk8Module

Add support for threeten-extra

Hi there is a project threeten-extra by @jodastephen that

provides additional date-time classes that complement those in Java SE 8.

I am interested in adding support for types defined there (especially Interval).
Ideally I would like this module to autodetect if three-ten is present and register serializers/deserializers.

But maybe it should live in its own separate module, WDYT?

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.

How does one pull the "datetime" package binaries into a Maven/Gradle project?

This source repository contains a "datetime" sub-project, which includes the JSR310 code migrated from its earlier (now deprecated) repo. https://github.com/FasterXML/jackson-datatype-jsr310

However, the datetime package does NOT appear to be included in the binary version of the com.fasterxml.jackson.module:jackson-modules-java8 library available on Maven Central.

Was this an accidental omission, or am I missing something here? How does one utilize the datetime package binaries in a Maven/Gradel project?

Inconsistent serialization/deserialization for OffsetDateTime

With WRITE_DATES_AS_TIMESTAMPS disabled, the serialization of OffsetDateTime preserves the offset, while the deserialization discards it (makes the offset 0 as UTC). The unit test below converts an a OffsetDateTime to JSON and back to OffsetDateTime. It fails because of this inconsistency.

NOTE: The OffsetDateTime corresponds to the same Instant, but it's not equal to the original OffsetDateTime as defined by equals(), which takes the offset into account.

    @Test
    public void offsetDateTimeToJsonAndBack() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new JavaTimeModule())
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        OffsetDateTime expected = OffsetDateTime.now();
        String actualString = objectMapper.writeValueAsString(expected);
        System.out.println(actualString); // 2018-02-06T08:37:33.557-08:00
        OffsetDateTime actual = objectMapper.readValue(actualString, OffsetDateTime.class);
        assertEquals(expected, actual);
    }

Failure:

java.lang.AssertionError:
Expected :2018-02-06T08:37:33.557-08:00
Actual   :2018-02-06T16:37:33.557Z

A similar test using parse() and toString() works fine:

    @Test
    public void offsetDateTimeToStringAndBack() {
        OffsetDateTime expected = OffsetDateTime.now();
        String actualString = expected.toString();
        System.out.println(actualString);
        OffsetDateTime actual = OffsetDateTime.parse(actualString);
        assertEquals(expected, actual);
    }

LocalDateDeserializer ignores local time zone

I expect the LocalDateDeserializer to return a date in the local time zone.

Following snippet can reproduce the wrong behaviour.

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
LocalDate date = mapper.readValue("\"2017-12-31T23:00:00.000Z\"", LocalDate.class);
System.out.println(date); // Output: 2017-12-31

IMHO should this code executed in GMT+1 print "2018-01-01".
This could be easily fixed. Replace this line:

// com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer.deserialize(JsonParser, DeserializationContext)
...
// replace this
return LocalDateTime.ofInstant(Instant.parse(string), ZoneOffset.UTC).toLocalDate();
// with this
return ZonedDateTime.parse(string).withZoneSameInstant(ZoneId.systemDefault()).toLocalDate();

Single argument constructor with single parameter does not work ?

Hi,

I have a case where I want to create an immutable object through constructor. The problem I found is that it get passed a null value first, then after try to set the field with the correct one, is this intended behavior ?

Here is a simple test to help you understand the problem:

public class JacksonTest {

    @Test
    public void TestReadValue() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper()
                .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
                .setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.PUBLIC_ONLY)
                .registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));

        ImmutableIdentity identity = objectMapper.readValue("{\"id\":\"ABCDEF\"}", ImmutableIdentity.class);

        assertEquals("ABCDEF", identity.id);
    }

    private static final class ImmutableIdentity {

        private final String id;

        public ImmutableIdentity(final String id) {
            Objects.requireNonNull(id, "The id must not be null.");

            this.id = id;
        }
    }

}

Here is the stack

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of JacksonTest$ImmutableIdentity, problem: The id must not be null.
at [Source: {"id":"ABCDEF"}; line: 1, column: 15]

If I comment the Objects.requiredNonNull, the test passes. But I need that in my constructor since its part of my serialized Domain Events.

Any help is welcome. Also if its not supported I can work on a patch if you give me some guidelines.

Double array serialization of `LocalDate` stored as an object with wrapper object typing enabled

version: 2.9.2

For some reason localdate is serialized as double array

public class ObjectMapperTest {

    private ObjectMapper objectMapper;

    @Before
    public void setUp() throws Exception {
        StdTypeResolverBuilder typeResolverBuilder =
                new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL)
                        .init(JsonTypeInfo.Id.CLASS, null)
                        .inclusion(JsonTypeInfo.As.WRAPPER_OBJECT);

        objectMapper = new ObjectMapper();
        objectMapper.setDefaultTyping(typeResolverBuilder);
        objectMapper.findAndRegisterModules();
    }

    @Test
    public void testLocalDateSerialization() throws JsonProcessingException {
        final LocalDate localDate = LocalDate.of(2017, 12, 5);
        String dateHolderStr = objectMapper.writeValueAsString(new Holder(localDate, localDate));
        Assert.assertEquals("{\"localDate\":[2017,12,5],\"object\":{\"java.time.LocalDate\":[2017,12,5]}}", dateHolderStr);
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
    private static class Holder {
        @JsonProperty
        private LocalDate localDate;
        @JsonProperty
        private Object object;
        public Holder(LocalDate localDate, Object object) {
            this.localDate = localDate;
            this.object = object;
        }
    }
}
Expected :{"localDate":[2017,12,5],"object":{"java.time.LocalDate":[2017,12,5]}}
Actual   :{"localDate":[2017,12,5],"object":{"java.time.LocalDate":[[2017,12,5]]}}

findCreatorAnnotation and findCreatorBinding on ParameterNamesAnnotationIntrospector behave differently

I'm trying to upgrade Spring Boot to Jackson 2.9.0.pr1, addressing deprecation warnings as I do so.

In a test we call findCreatorBinding on ParameterNamesAnnotationIntrospector to verify that it's been configured correctly. When the Annotated that is passed in has no creator annotation, the introspector's binding is returned. If, as recommended, I change to calling findCreatorAnnotation then null is returned instead.

The difference in behaviour between the two methods was introduced in this commit but I can't tell if it was intentional and we should change our test, or if it wasn't and a change in Jackson is warranted.

Documentation mistake on module auto-registration

The javadoc on JavaTimeModule says:

Note that as of 2.6, this module does NOT support auto-registration, because of existence of legacy version, {@link JSR310Module}.

and the documentation shows that auto-registration is an option, this should be removed then.

Disabling WRITE_DATES_AS_TIMESTAMPS not working?

According to the /datetime/ summary, java.time.offsetDateTime objects should serialize out to ISO-8601 strings simply by disabling the SerializationFeature#WRITE_DATES_AS_TIMESTAMPS feature?

I've tried to do this in two different ways in the JacksonConfig class:

ObjectMapper mapper = new ObjectMapper()
            .registerModule(new ParameterNamesModule())
            .registerModule(new Jdk8Module())
            .registerModule(new JavaTimeModule())
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
@Bean
@Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper
                .registerModule(new ParameterNamesModule())
                .registerModule(new Jdk8Module())
                .registerModule(new JavaTimeModule())
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        return objectMapper;
}

In both cases, serialized offsetDateTime objects continue to be printed in nanosecond timestamp format (i.e. "expiresOn":1486138656.853000000).

Only by actually declaring @jsonformat can I get them to print out in ISO-8601 string.

Is anyone else having this issue?

Allow `LocalDate` to be serialized/deserialized as number (epoch day)

The LocalDateSerializer only supports serializing LocalDate instances as strings or arrays, both of which suffer from localization issues ("is it year-month-day or year-day-month?").

An alternative would be to use LocalDate#toEpochDay() and LocalDate#ofEpochDay(long), thus allowing a (less human readable but) unambiguous format.

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.

Cached `Optional` serializer does not apply annotations for POJO properties

Simple test case:

public class JsonObjectTest {
    
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
    private Optional<Date> date1;
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM")
    private Optional<Date> date2;
    
    public JsonObjectTest () {}

    public Optional<Date> getDate1() {
        return date1;
    }
    public void setDate1(Optional<Date> date1) {
        this.date1 = date1;
    }
    public Optional<Date> getDate2() {
        return date2;
    }
    public void setDate2(Optional<Date> date2) {
        this.date2 = date2;
    }
    
}
public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        JsonObjectTest t = new JsonObjectTest();
        t.setDate1(Optional.ofNullable(new Date()));
        t.setDate2(Optional.ofNullable(new Date()));
        ObjectMapper mapper = new ObjectMapper().registerModule(new Jdk8Module());
        System.out.println(mapper.writeValueAsString(t));
    }
}

produces the output:
{"date1":"2017-03-10","date2":"2017-03-10"}

changing the order of the members to:

    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM")
    private Optional<Date> date2;
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
    private Optional<Date> date1;

produces the output:
{"date2":"2017-03","date1":"2017-03"}

I have found that the issue lies in OptionalSerializer.java on the ser = _findCachedSerializer(provider, value.getClass()); line. The first time a Date is encountered, a new serializer is constructed which observes the relevant annotation; however, every time a Date is encountered afterwards the cached serializer is used without any consideration regarding annotation differences.

Cannot parse ISO 8601 "style" date that does not contain colon in offset

java.util.Date works fine, but does not work for the new time types when the offset is +0500 vs +05:00

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.Date;

public class JavaDateTest {
    private static final String json = "{\"happyHour\": \"2016-01-01T00:00:00.000+5000\"}";

    private ObjectMapper objectMapper;

    @Before
    public void setup() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
    }

    @Test
    public void date() throws IOException {
        objectMapper.readValue(json, DtoWithDate.class); //pass
    }

    @Test
    public void instant() throws IOException {
        objectMapper.readValue(json, DtoWithInstant.class); //fail
    }

    @Test
    public void offset() throws IOException {
        objectMapper.readValue(json, DtoWithOffsetDateTime.class); //fail
    }

    private static class DtoWithDate {
        private Date happyHour;

        public Date getHappyHour() {
            return happyHour;
        }

        public void setHappyHour(Date happyHour) {
            this.happyHour = happyHour;
        }
    }

    private static class DtoWithInstant {
        private Instant happyHour;

        public Instant getHappyHour() {
            return happyHour;
        }

        public void setHappyHour(Instant happyHour) {
            this.happyHour = happyHour;
        }
    }

    private static class DtoWithOffsetDateTime {
        private OffsetDateTime happyHour;

        public OffsetDateTime getHappyHour() {
            return happyHour;
        }

        public void setHappyHour(OffsetDateTime happyHour) {
            this.happyHour = happyHour;
        }
    }
}



Unable to deserialize with 'jackson-module-parameter-names', version: '2.9.4'

We are getting following exception with our multimodule project.

Can not construct instance of com.myproject.events.CategoryCreatedEvent: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: N/A; line: -1, column: -1]

The project structure looks as following:

myproject
`- api
`- events
`- projections

Within projections we try to deserialize the event which is published by api via RabbitMQ. To consume those events we are using our own library which contains the pub sub logic. Also this library has the same jackson-module-parameter-names module included and has the -parameters compiler argument.

Within the unittests in this rabbitmq project the deserializing is actually working. Within this projections library projects it gives the above error.

Is there a limitation on using this accross multiple modules?

We are running it using JDK 1.8.0_152.

Provide ParamConverterProvider implementations

Please provide ParamConverterProvider implementations for all date/time types that make sense to use as query parameters. For example, here is an implementation I used to pass LocalDateTime as a query parameter (feel free to use it):

/**
 * Enables LocalDateTime to be used as a QueryParam.
 *
 * @author Gili Tzabari
 */
@Provider
@Singleton
public class LocalDateTimeConverter implements ParamConverterProvider {
    @Override public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {

        if (!rawType.isAssignableFrom(LocalDateTime.class)) {
            return null;
        }
        return new ParamConverter<T>() {
            @Override
            @SuppressWarnings("unchecked")
            public T fromString(final String value) {
                if (value == null) {
                    throw new IllegalArgumentException("value may not be null");
                }
                return (T) LocalDateTime.parse(value);
            }

            @Override
            public String toString(final T value) {
                return ((LocalDateTime) value).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
            }
        };
    }
}

Jdk8Module support for java8 Date classes serialization

I'm moving from JSR310Module to Jdk8Module and have noticed that Jdk8Module is not supporting java8 Date classes (LocalDateTime, etc.)

So my old JSR310 code that is producing correct date/time format is:

mapper.registerModule(new JSR310Module());
mapper.setDateFormat(new ISO8601DateFormat());

so it generates "2017-07-28T12:32:39.927"

But when I move to Jdk8Module:

mapper.registerModule(new Jdk8Module());
mapper.setDateFormat(new ISO8601DateFormat());

it generates "{"year":2017,"month":"JULY","dayOfYear":209,"dayOfWeek":"FRIDAY","dayOfMonth":28,"monthValue":7,"hour":12,"minute":45,"second":53,"nano":458000000,"chronology":{"id":"ISO","calendarType":"iso8601"}}"

After looking around in the docs looks like Jdk8Module isn't supporting Java8 new date classes. Instead I have to use JSR310's JavaTime module for that:

mapper.registerModule(new Jdk8Module());
mapper.registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule());
mapper.setDateFormat(new ISO8601DateFormat());

so it generates "2017-07-28T12:32:39.927"

Can you please implement support for java8 new Date classes in Jdk8Module (not in some other module)?

Thank you in advance!

Optional fields do not respect the Include.NON_NULL property [2.9.4]

The below output is {bar:null}. The expected output is {}.

public class Example {


  static final class Foo {
    private final Integer bar;
    private final String moo;

    Foo(Integer bar, String moo) {
      this.bar = bar;
      this.moo = moo;
    }

    public Optional<Integer> getBar() {
      return Optional.ofNullable(bar);
    }

    public String getMoo() {
      return moo;
    }
  }

  public static void main(String[] args) {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    objectMapper.registerModule(new Jdk8Module());
    try {
      System.out.println(objectMapper.writeValueAsString(new Foo(null, null)));
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.