jackson-modules-java8 icon indicating copy to clipboard operation
jackson-modules-java8 copied to clipboard

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

Open dmoses48 opened this issue 6 years ago • 1 comments

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;
		}
	}

dmoses48 avatar Mar 26 '18 17:03 dmoses48

Ok, I see what the intent is. Not sure how to address it yet, but whatever approach taken would go in 3.0 (since that's next after 2.9).

cowtowncoder avatar Apr 10 '18 04:04 cowtowncoder