Skip to content

Commit

Permalink
[Feature #10] Implement and use custom (de)serializers for TemporalAm…
Browse files Browse the repository at this point in the history
…ount values.

Add integration test ensuring serialization and deserialization are compatible.
  • Loading branch information
ledsoft committed Mar 22, 2022
1 parent d6472be commit d410cef
Show file tree
Hide file tree
Showing 16 changed files with 315 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ private void initBuiltInDeserializers() {
deserializers.put(OffsetTime.class, coreTimeDeserializer);
deserializers.put(LocalTime.class, new LocalTimeDeserializer(coreTimeDeserializer));
deserializers.put(LocalDate.class, new LocalDateDeserializer());
// TODO Period, Duration
deserializers.put(Duration.class, new DurationDeserializer());
deserializers.put(Period.class, new PeriodDeserializer());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cz.cvut.kbss.jsonld.deserialization.datetime;

import cz.cvut.kbss.jsonld.deserialization.DeserializationContext;
import cz.cvut.kbss.jsonld.deserialization.ValueDeserializer;
import cz.cvut.kbss.jsonld.exception.JsonLdDeserializationException;

import java.time.Duration;
import java.util.Map;

import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializer.getLiteralValue;

/**
* Deserializes JSON values to {@link Duration}.
* <p>
* The value is expected to be an ISO 8601-formatted string.
*/
public class DurationDeserializer implements ValueDeserializer<Duration> {

@Override
public Duration deserialize(Map<?, ?> jsonNode, DeserializationContext<Duration> ctx) {
final Object value = getLiteralValue(jsonNode);
try {
return Duration.parse(value.toString());
} catch (RuntimeException e) {
throw new JsonLdDeserializationException("Unable to deserialize duration.", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package cz.cvut.kbss.jsonld.deserialization.datetime;

import cz.cvut.kbss.jopa.datatype.xsd.XsdDateMapper;
import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.jsonld.deserialization.DeserializationContext;
import cz.cvut.kbss.jsonld.deserialization.ValueDeserializer;

import java.time.LocalDate;
import java.util.Map;

import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializer.missingValueException;
import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializer.getLiteralValue;

/**
* Deserializes values to {@link LocalDate}.
Expand All @@ -19,10 +18,7 @@ public class LocalDateDeserializer implements ValueDeserializer<LocalDate> {

@Override
public LocalDate deserialize(Map<?, ?> jsonNode, DeserializationContext<LocalDate> ctx) {
final Object value = jsonNode.get(JsonLd.VALUE);
if (value == null) {
throw missingValueException(jsonNode);
}
final Object value = getLiteralValue(jsonNode);
return XsdDateMapper.map(value.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ public class OffsetDateTimeDeserializer implements ValueDeserializer<OffsetDateT

@Override
public OffsetDateTime deserialize(Map<?, ?> jsonNode, DeserializationContext<OffsetDateTime> ctx) {
final Object value = jsonNode.get(JsonLd.VALUE);
if (value == null) {
throw missingValueException(jsonNode);
}
final Object value = getLiteralValue(jsonNode);
return value instanceof Long ? epochResolver.resolve((Long) value) : stringResolver.resolve(value.toString());
}

static JsonLdDeserializationException missingValueException(Map<?, ?> jsonNode) {
return new JsonLdDeserializationException("Cannot deserialize node " + jsonNode + "as literal. " +
"It is missing attribute " + JsonLd.VALUE + ".");
static Object getLiteralValue(Map<?, ?> jsonNode) {
final Object value = jsonNode.get(JsonLd.VALUE);
if (value == null) {
throw new JsonLdDeserializationException("Cannot deserialize node " + jsonNode + "as literal. " +
"It is missing attribute '" + JsonLd.VALUE + "'.");
}
return value;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package cz.cvut.kbss.jsonld.deserialization.datetime;

import cz.cvut.kbss.jopa.datatype.xsd.XsdTimeMapper;
import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.jsonld.deserialization.DeserializationContext;
import cz.cvut.kbss.jsonld.deserialization.ValueDeserializer;

import java.time.OffsetTime;
import java.util.Map;

import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializer.missingValueException;
import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializer.getLiteralValue;

/**
* Deserializes values to {@link OffsetTime}.
Expand All @@ -19,10 +18,7 @@ public class OffsetTimeDeserializer implements ValueDeserializer<OffsetTime> {

@Override
public OffsetTime deserialize(Map<?, ?> jsonNode, DeserializationContext<OffsetTime> ctx) {
final Object value = jsonNode.get(JsonLd.VALUE);
if (value == null) {
throw missingValueException(jsonNode);
}
final Object value = getLiteralValue(jsonNode);
return XsdTimeMapper.map(value.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cz.cvut.kbss.jsonld.deserialization.datetime;

import cz.cvut.kbss.jsonld.deserialization.DeserializationContext;
import cz.cvut.kbss.jsonld.deserialization.ValueDeserializer;
import cz.cvut.kbss.jsonld.exception.JsonLdDeserializationException;

import java.time.Period;
import java.util.Map;

import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializer.getLiteralValue;

/**
* Deserializes JSON values to {@link Period}.
* <p>
* The value is expected to be an ISO 8601-formatted string.
*/
public class PeriodDeserializer implements ValueDeserializer<Period> {

@Override
public Period deserialize(Map<?, ?> jsonNode, DeserializationContext<Period> ctx) {
final Object value = getLiteralValue(jsonNode);
try {
return Period.parse(value.toString());
} catch (RuntimeException e) {
throw new JsonLdDeserializationException("Unable to deserialize duration.", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
/**
* Copyright (C) 2022 Czech Technical University in Prague
* <p>
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
* version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details. You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details. You should have received a copy of the GNU General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*/
package cz.cvut.kbss.jsonld.serialization;

import cz.cvut.kbss.jsonld.Configuration;
import cz.cvut.kbss.jsonld.serialization.datetime.DateSerializer;
import cz.cvut.kbss.jsonld.serialization.datetime.TemporalAmountSerializer;
import cz.cvut.kbss.jsonld.serialization.datetime.TemporalSerializer;
import cz.cvut.kbss.jsonld.serialization.traversal.SerializationContext;

Expand Down Expand Up @@ -46,6 +45,9 @@ private void initBuiltInSerializers() {
serializers.put(ZonedDateTime.class, ts);
serializers.put(Instant.class, ts);
serializers.put(Date.class, new DateSerializer(ts));
final TemporalAmountSerializer tas = new TemporalAmountSerializer();
serializers.put(Duration.class, tas);
serializers.put(Period.class, tas);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cz.cvut.kbss.jsonld.serialization.datetime;

import cz.cvut.kbss.jsonld.serialization.JsonNodeFactory;
import cz.cvut.kbss.jsonld.serialization.ValueSerializer;
import cz.cvut.kbss.jsonld.serialization.model.JsonNode;
import cz.cvut.kbss.jsonld.serialization.traversal.SerializationContext;

import java.time.temporal.TemporalAmount;

/**
* Serializes {@link TemporalAmount} instances ({@link java.time.Duration}, {@link java.time.Period}) to JSON string in
* the ISO 8601 format.
*/
public class TemporalAmountSerializer implements ValueSerializer<TemporalAmount> {

@Override
public JsonNode serialize(TemporalAmount value, SerializationContext<TemporalAmount> ctx) {
return JsonNodeFactory.createLiteralNode(ctx.getAttributeId(), value.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cz.cvut.kbss.jsonld.deserialization.datetime;

import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.jsonld.environment.Generator;
import cz.cvut.kbss.jsonld.exception.JsonLdDeserializationException;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.Map;

import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializerTest.deserializationContext;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.*;

class DurationDeserializerTest {

private final DurationDeserializer sut = new DurationDeserializer();

@Test
void deserializeDeserializesIsoStringToDuration() {
final Duration value = Duration.ofSeconds(Generator.randomCount(5, 10000));
final Map<String, Object> input = Collections.singletonMap(JsonLd.VALUE, value.toString());
final Duration result = sut.deserialize(input, deserializationContext(Duration.class));
assertEquals(value, result);
}

@Test
void deserializeThrowsJsonLdDeserializationExceptionWhenInputIsMissingValueAttribute() {
final Map<String, Object> input = Collections.singletonMap("notValue", Duration.ofSeconds(100));
final JsonLdDeserializationException ex = assertThrows(JsonLdDeserializationException.class,
() -> sut.deserialize(input, deserializationContext(
Duration.class)));
assertThat(ex.getMessage(), containsString(JsonLd.VALUE));
assertThat(ex.getMessage(), containsString("missing"));
}

@Test
void deserializeThrowsJsonLdDeserializationExceptionWhenInputIsNotInIsoFormat() {
final Map<String, Object> input = Collections.singletonMap(JsonLd.VALUE, "NotValid");
final JsonLdDeserializationException ex = assertThrows(JsonLdDeserializationException.class,
() -> sut.deserialize(input, deserializationContext(
Duration.class)));
assertInstanceOf(DateTimeParseException.class, ex.getCause());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cz.cvut.kbss.jsonld.deserialization.datetime;

import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.jsonld.environment.Generator;
import cz.cvut.kbss.jsonld.exception.JsonLdDeserializationException;
import org.junit.jupiter.api.Test;

import java.time.Period;
import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.Map;

import static cz.cvut.kbss.jsonld.deserialization.datetime.OffsetDateTimeDeserializerTest.deserializationContext;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.*;

class PeriodDeserializerTest {

private final PeriodDeserializer sut = new PeriodDeserializer();

@Test
void deserializeDeserializesIsoStringToPeriod() {
final Period value =
Period.of(Generator.randomCount(5, 100), Generator.randomCount(1, 12), Generator.randomCount(1, 28));
final Map<String, Object> input = Collections.singletonMap(JsonLd.VALUE, value.toString());
final Period result = sut.deserialize(input, deserializationContext(Period.class));
assertEquals(value, result);
}

@Test
void deserializeThrowsJsonLdDeserializationExceptionWhenInputIsMissingValueAttribute() {
final Map<String, Object> input = Collections.singletonMap("notValue", Period.ofMonths(8));
final JsonLdDeserializationException ex = assertThrows(JsonLdDeserializationException.class,
() -> sut.deserialize(input, deserializationContext(
Period.class)));
assertThat(ex.getMessage(), containsString(JsonLd.VALUE));
assertThat(ex.getMessage(), containsString("missing"));
}

@Test
void deserializeThrowsJsonLdDeserializationExceptionWhenInputIsNotInIsoFormat() {
final Map<String, Object> input = Collections.singletonMap(JsonLd.VALUE, "NotValid");
final JsonLdDeserializationException ex = assertThrows(JsonLdDeserializationException.class,
() -> sut.deserialize(input, deserializationContext(
Period.class)));
assertInstanceOf(DateTimeParseException.class, ex.getCause());
}
}
5 changes: 5 additions & 0 deletions src/test/java/cz/cvut/kbss/jsonld/environment/Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import cz.cvut.kbss.jsonld.environment.model.Organization;
import cz.cvut.kbss.jsonld.environment.model.Person;
import cz.cvut.kbss.jsonld.environment.model.User;
import cz.cvut.kbss.jsonld.serialization.traversal.SerializationContext;

import java.net.URI;
import java.util.*;
Expand Down Expand Up @@ -142,4 +143,8 @@ public static Map<String, Set<String>> generateProperties(boolean singletons) {
}
return map;
}

public static <T> SerializationContext<T> serializationContext(T value) {
return new SerializationContext<>(generateUri().toString(), value);
}
}
Loading

0 comments on commit d410cef

Please sign in to comment.