Skip to content

Commit 3b30098

Browse files
committed
[Enhancement #66] Implement serialization of numeric values with datatype for CompactedJsonLdSerializer.
1 parent ebb01ce commit 3b30098

File tree

9 files changed

+193
-35
lines changed

9 files changed

+193
-35
lines changed

src/main/java/cz/cvut/kbss/jsonld/serialization/CompactedJsonLdSerializer.java

+12-10
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,21 @@
2424
import cz.cvut.kbss.jsonld.serialization.serializer.LiteralValueSerializers;
2525
import cz.cvut.kbss.jsonld.serialization.serializer.ObjectGraphValueSerializers;
2626
import cz.cvut.kbss.jsonld.serialization.serializer.ValueSerializers;
27-
import cz.cvut.kbss.jsonld.serialization.serializer.compact.*;
27+
import cz.cvut.kbss.jsonld.serialization.serializer.compact.DefaultValueSerializer;
28+
import cz.cvut.kbss.jsonld.serialization.serializer.compact.IdentifierSerializer;
29+
import cz.cvut.kbss.jsonld.serialization.serializer.compact.IndividualSerializer;
30+
import cz.cvut.kbss.jsonld.serialization.serializer.compact.MultilingualStringSerializer;
31+
import cz.cvut.kbss.jsonld.serialization.serializer.compact.NumberSerializer;
32+
import cz.cvut.kbss.jsonld.serialization.serializer.compact.ObjectPropertyValueSerializer;
33+
import cz.cvut.kbss.jsonld.serialization.serializer.compact.TypesSerializer;
2834
import cz.cvut.kbss.jsonld.serialization.serializer.compact.datetime.TemporalAmountSerializer;
2935
import cz.cvut.kbss.jsonld.serialization.serializer.compact.datetime.TemporalSerializer;
3036
import cz.cvut.kbss.jsonld.serialization.serializer.datetime.DateSerializer;
3137
import cz.cvut.kbss.jsonld.serialization.traversal.ObjectGraphTraverser;
3238
import cz.cvut.kbss.jsonld.serialization.traversal.SerializationContextFactory;
3339

34-
import java.time.*;
40+
import java.time.Duration;
41+
import java.time.Period;
3542
import java.util.Date;
3643

3744
/**
@@ -57,19 +64,14 @@ protected ValueSerializers initSerializers() {
5764
valueSerializers.registerTypesSerializer(new TypesSerializer());
5865
valueSerializers.registerIndividualSerializer(new IndividualSerializer());
5966
final TemporalSerializer ts = new TemporalSerializer();
60-
valueSerializers.registerSerializer(LocalDate.class, ts);
6167
// Register the same temporal serializer for each of the types it supports (needed for key-based map access)
62-
valueSerializers.registerSerializer(LocalDate.class, ts);
63-
valueSerializers.registerSerializer(LocalTime.class, ts);
64-
valueSerializers.registerSerializer(OffsetTime.class, ts);
65-
valueSerializers.registerSerializer(LocalDateTime.class, ts);
66-
valueSerializers.registerSerializer(OffsetDateTime.class, ts);
67-
valueSerializers.registerSerializer(ZonedDateTime.class, ts);
68-
valueSerializers.registerSerializer(Instant.class, ts);
68+
TemporalSerializer.getSupportedTypes().forEach(cls -> valueSerializers.registerSerializer(cls, ts));
6969
valueSerializers.registerSerializer(Date.class, new DateSerializer(ts));
7070
final TemporalAmountSerializer tas = new TemporalAmountSerializer();
7171
valueSerializers.registerSerializer(Duration.class, tas);
7272
valueSerializers.registerSerializer(Period.class, tas);
73+
final NumberSerializer numberSerializer = new NumberSerializer();
74+
NumberSerializer.getSupportedTypes().forEach(cls -> valueSerializers.registerSerializer(cls, numberSerializer));
7375
return valueSerializers;
7476
}
7577

src/main/java/cz/cvut/kbss/jsonld/serialization/JsonNodeFactory.java

+9-16
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,11 @@ public static LiteralNode<?> createLiteralNode(Object value) {
4343

4444
public static LiteralNode<?> createLiteralNode(String name, Object value) {
4545
final LiteralType type = determineLiteralType(value);
46-
switch (type) {
47-
case BOOLEAN:
48-
return createBooleanLiteralNode(name, (Boolean) value);
49-
case NUMBER:
50-
return createNumericLiteralNode(name, (Number) value);
51-
default:
52-
return createStringLiteralNode(name, value.toString());
53-
}
46+
return switch (type) {
47+
case BOOLEAN -> createBooleanLiteralNode(name, (Boolean) value);
48+
case NUMBER -> createNumericLiteralNode(name, (Number) value);
49+
default -> createStringLiteralNode(name, value.toString());
50+
};
5451
}
5552

5653
private static LiteralType determineLiteralType(Object value) {
@@ -84,14 +81,10 @@ public static StringLiteralNode createStringLiteralNode(String name, String valu
8481
*/
8582
public static CollectionNode<?> createCollectionNode(String name, Collection<?> value) {
8683
final CollectionType type = determineCollectionType(value);
87-
switch (type) {
88-
case LIST:
89-
return new ListNode(name);
90-
case SET:
91-
return createSetNode(name);
92-
default:
93-
throw new IllegalArgumentException("Unsupported collection type " + type);
94-
}
84+
return switch (type) {
85+
case LIST -> new ListNode(name);
86+
case SET -> createSetNode(name);
87+
};
9588
}
9689

9790
private static CollectionType determineCollectionType(Collection<?> collection) {

src/main/java/cz/cvut/kbss/jsonld/serialization/serializer/SerializerUtils.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public static ObjectNode createTypedTermDefinition(String term, String id, Strin
7070
* @param type Value type to use
7171
* @return Resulting JSON node
7272
*/
73-
public static JsonNode createdTypedValueNode(String term, String value, String type) {
73+
public static JsonNode createdTypedValueNode(String term, Object value, String type) {
7474
final ObjectNode node = JsonNodeFactory.createObjectNode(term);
7575
node.addItem(JsonNodeFactory.createLiteralNode(JsonLd.TYPE, type));
7676
node.addItem(JsonNodeFactory.createLiteralNode(JsonLd.VALUE, value));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package cz.cvut.kbss.jsonld.serialization.serializer.compact;
2+
3+
import cz.cvut.kbss.jopa.vocabulary.XSD;
4+
import cz.cvut.kbss.jsonld.serialization.model.JsonNode;
5+
import cz.cvut.kbss.jsonld.serialization.serializer.SerializerUtils;
6+
import cz.cvut.kbss.jsonld.serialization.serializer.ValueSerializer;
7+
import cz.cvut.kbss.jsonld.serialization.traversal.SerializationContext;
8+
9+
import java.math.BigDecimal;
10+
import java.math.BigInteger;
11+
import java.util.List;
12+
import java.util.Objects;
13+
14+
/**
15+
* Serializes numeric values.
16+
*/
17+
public class NumberSerializer implements ValueSerializer<Number> {
18+
19+
@Override
20+
public JsonNode serialize(Number value, SerializationContext<Number> ctx) {
21+
Objects.requireNonNull(value);
22+
if (value instanceof Integer) {
23+
return SerializerUtils.createdTypedValueNode(ctx.getTerm(), value, XSD.INT);
24+
} else if (value instanceof Long) {
25+
return SerializerUtils.createdTypedValueNode(ctx.getTerm(), value, XSD.LONG);
26+
} else if (value instanceof Double) {
27+
return SerializerUtils.createdTypedValueNode(ctx.getTerm(), value, XSD.DOUBLE);
28+
} else if (value instanceof Float) {
29+
return SerializerUtils.createdTypedValueNode(ctx.getTerm(), value, XSD.FLOAT);
30+
} else if (value instanceof Short) {
31+
return SerializerUtils.createdTypedValueNode(ctx.getTerm(), value, XSD.SHORT);
32+
} else if (value instanceof BigInteger) {
33+
return SerializerUtils.createdTypedValueNode(ctx.getTerm(), value, XSD.INTEGER);
34+
} else if (value instanceof BigDecimal) {
35+
return SerializerUtils.createdTypedValueNode(ctx.getTerm(), value, XSD.DECIMAL);
36+
} else {
37+
throw new IllegalArgumentException("Unsupported numeric literal type " + value.getClass());
38+
}
39+
}
40+
41+
/**
42+
* Gets a list of Java types supported by this serializer.
43+
*
44+
* @return List of Java classes
45+
*/
46+
public static List<Class<? extends Number>> getSupportedTypes() {
47+
return List.of(Integer.class, Long.class, Double.class, Float.class, Short.class, BigInteger.class,
48+
BigDecimal.class);
49+
}
50+
}

src/main/java/cz/cvut/kbss/jsonld/serialization/serializer/compact/datetime/TemporalSerializer.java

+23-4
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,15 @@
2525
import cz.cvut.kbss.jsonld.serialization.serializer.datetime.DateTimeSerializer;
2626
import cz.cvut.kbss.jsonld.serialization.traversal.SerializationContext;
2727

28-
import java.time.*;
28+
import java.time.Instant;
29+
import java.time.LocalDate;
30+
import java.time.LocalDateTime;
31+
import java.time.LocalTime;
32+
import java.time.OffsetDateTime;
33+
import java.time.OffsetTime;
34+
import java.time.ZonedDateTime;
2935
import java.time.temporal.TemporalAccessor;
36+
import java.util.List;
3037

3138
/**
3239
* Serializes Java 8 date/time values represented by the {@link TemporalAccessor} interface.
@@ -41,7 +48,8 @@ public TemporalSerializer() {
4148
this(new IsoDateTimeSerializer(), new LocalDateSerializer(), new TimeSerializer());
4249
}
4350

44-
protected TemporalSerializer(DateTimeSerializer dateTimeSerializer, LocalDateSerializer dateSerializer, TimeSerializer timeSerializer) {
51+
protected TemporalSerializer(DateTimeSerializer dateTimeSerializer, LocalDateSerializer dateSerializer,
52+
TimeSerializer timeSerializer) {
4553
this.dateTimeSerializer = dateTimeSerializer;
4654
this.dateSerializer = dateSerializer;
4755
this.timeSerializer = timeSerializer;
@@ -64,14 +72,25 @@ public JsonNode serialize(TemporalAccessor value, SerializationContext<TemporalA
6472
} else if (value instanceof ZonedDateTime) {
6573
return dateTimeSerializer.serialize(((ZonedDateTime) value).toOffsetDateTime(), ctx);
6674
}
67-
throw new UnsupportedTemporalTypeException("Temporal type " + value.getClass() + " serialization is not supported.");
75+
throw new UnsupportedTemporalTypeException(
76+
"Temporal type " + value.getClass() + " serialization is not supported.");
6877
}
6978

7079
@Override
7180
public void configure(Configuration config) {
7281
assert config != null;
7382
this.dateTimeSerializer = config.is(ConfigParam.SERIALIZE_DATETIME_AS_MILLIS) ?
74-
new EpochBasedDateTimeSerializer() : new IsoDateTimeSerializer();
83+
new EpochBasedDateTimeSerializer() : new IsoDateTimeSerializer();
7584
dateTimeSerializer.configure(config);
7685
}
86+
87+
/**
88+
* Gets the Java types supported by this serializer.
89+
*
90+
* @return List of Java classes
91+
*/
92+
public static List<Class<? extends TemporalAccessor>> getSupportedTypes() {
93+
return List.of(LocalDate.class, OffsetTime.class, LocalTime.class, OffsetDateTime.class, LocalDateTime.class,
94+
Instant.class, ZonedDateTime.class);
95+
}
7796
}

src/test/java/cz/cvut/kbss/jsonld/environment/model/PersonWithTypedProperties.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,13 @@ public void toRdf(Model model, ValueFactory vf, Set<URI> visited) {
8787
model.add(id, vf.createIRI(Vocabulary.LAST_NAME), vf.createLiteral(lastName));
8888
if (properties != null) {
8989
properties.forEach((k, v) -> v.forEach(o -> {
90-
if (o instanceof GeneratesRdf) {
91-
final GeneratesRdf entity = (GeneratesRdf) o;
90+
if (o instanceof GeneratesRdf entity) {
9291
model.add(id, vf.createIRI(k.toString()), vf.createIRI(entity.getUri().toString()));
9392
entity.toRdf(model, vf, visited);
9493
} else if (o instanceof URI) {
9594
model.add(id, vf.createIRI(k.toString()), vf.createIRI(o.toString()));
9695
} else if (o instanceof Integer) {
97-
model.add(id, vf.createIRI(k.toString()), vf.createLiteral(o.toString(), vf.createIRI(XSD.INTEGER)));
96+
model.add(id, vf.createIRI(k.toString()), vf.createLiteral(o.toString(), vf.createIRI(XSD.INT)));
9897
} else {
9998
model.add(id, vf.createIRI(k.toString()), vf.createLiteral(o.toString()));
10099
}

src/test/java/cz/cvut/kbss/jsonld/environment/model/Study.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public void toRdf(Model model, ValueFactory vf, Set<URI> visited) {
108108
model.add(id, RDF.TYPE, vf.createIRI(Vocabulary.STUDY));
109109
model.add(id, vf.createIRI(RDFS.LABEL), vf.createLiteral(name));
110110
model.add(id, vf.createIRI(Vocabulary.NUMBER_OF_PEOPLE_INVOLVED),
111-
vf.createLiteral(noOfPeopleInvolved.toString(), vf.createIRI(XSD.INTEGER)));
111+
vf.createLiteral(noOfPeopleInvolved.toString(), vf.createIRI(XSD.INT)));
112112
if (members != null) {
113113
members.forEach(m -> {
114114
model.add(id, vf.createIRI(Vocabulary.HAS_MEMBER), vf.createIRI(m.getUri().toString()));

src/test/java/cz/cvut/kbss/jsonld/serialization/JsonLdSerializerTestBase.java

+47
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
package cz.cvut.kbss.jsonld.serialization;
1919

2020
import cz.cvut.kbss.jopa.model.annotations.Id;
21+
import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty;
2122
import cz.cvut.kbss.jopa.model.annotations.OWLClass;
2223
import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
24+
import cz.cvut.kbss.jopa.vocabulary.DC;
25+
import cz.cvut.kbss.jopa.vocabulary.XSD;
2326
import cz.cvut.kbss.jsonld.ConfigParam;
2427
import cz.cvut.kbss.jsonld.JsonLd;
2528
import cz.cvut.kbss.jsonld.common.IdentifierUtil;
@@ -43,8 +46,11 @@
4346
import cz.cvut.kbss.jsonld.serialization.serializer.ValueSerializer;
4447
import cz.cvut.kbss.jsonld.serialization.util.BufferedJsonGenerator;
4548
import jakarta.json.Json;
49+
import jakarta.json.JsonArray;
50+
import jakarta.json.JsonObject;
4651
import jakarta.json.JsonValue;
4752
import org.eclipse.rdf4j.model.BNode;
53+
import org.eclipse.rdf4j.model.IRI;
4854
import org.eclipse.rdf4j.model.Model;
4955
import org.eclipse.rdf4j.model.Statement;
5056
import org.eclipse.rdf4j.model.ValueFactory;
@@ -73,6 +79,7 @@
7379
import java.util.stream.IntStream;
7480

7581
import static cz.cvut.kbss.jsonld.environment.IsIsomorphic.isIsomorphic;
82+
import static cz.cvut.kbss.jsonld.environment.TestUtil.parseAndExpand;
7683
import static org.hamcrest.MatcherAssert.assertThat;
7784
import static org.hamcrest.Matchers.hasItem;
7885
import static org.hamcrest.Matchers.hasItems;
@@ -473,4 +480,44 @@ void serializationSerializesAttributeWithCollectionOfEnumConstantsMappedToIndivi
473480
final Model actual = readJson(jsonWriter.getResult());
474481
assertThat(actual, isIsomorphic(expected));
475482
}
483+
484+
@Test
485+
void serializationIncludesDatatypeOfNumericLiterals() throws Exception {
486+
final Product p = new Product();
487+
p.price = 155.15;
488+
p.name = "Test product";
489+
p.uri = Generator.generateUri();
490+
sut.serialize(p);
491+
final JsonArray result = parseAndExpand(jsonWriter.getResult());
492+
final JsonObject obj = result.getJsonObject(0);
493+
final JsonArray priceAtt = obj.getJsonArray("https://schema.org/price");
494+
assertEquals(1, priceAtt.size());
495+
assertEquals(XSD.DOUBLE, priceAtt.getJsonObject(0).getString("@type"));
496+
assertEquals(p.price.toString(), priceAtt.getJsonObject(0).getJsonNumber("@value").toString());
497+
}
498+
499+
@OWLClass(iri = Vocabulary.DEFAULT_PREFIX + "Product")
500+
private static class Product implements GeneratesRdf {
501+
@Id
502+
private URI uri;
503+
504+
@OWLAnnotationProperty(iri = DC.Terms.TITLE)
505+
private String name;
506+
507+
@OWLDataProperty(iri = "https://schema.org/price")
508+
private Double price;
509+
510+
@Override
511+
public URI getUri() {
512+
return uri;
513+
}
514+
515+
@Override
516+
public void toRdf(Model model, ValueFactory vf, Set<URI> visited) {
517+
final IRI subject = vf.createIRI(uri.toString());
518+
model.add(subject, RDF.TYPE, vf.createIRI(Vocabulary.DEFAULT_PREFIX + "Product"));
519+
model.add(subject, vf.createIRI(DC.Terms.TITLE), vf.createLiteral(name));
520+
model.add(subject, vf.createIRI("https://schema.org/price"), vf.createLiteral(price));
521+
}
522+
}
476523
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cz.cvut.kbss.jsonld.serialization.serializer.compact;
2+
3+
import cz.cvut.kbss.jopa.vocabulary.XSD;
4+
import cz.cvut.kbss.jsonld.JsonLd;
5+
import cz.cvut.kbss.jsonld.serialization.JsonNodeFactory;
6+
import cz.cvut.kbss.jsonld.serialization.context.DummyJsonLdContext;
7+
import cz.cvut.kbss.jsonld.serialization.model.JsonNode;
8+
import cz.cvut.kbss.jsonld.serialization.model.ObjectNode;
9+
import cz.cvut.kbss.jsonld.serialization.traversal.SerializationContext;
10+
import org.junit.jupiter.params.ParameterizedTest;
11+
import org.junit.jupiter.params.provider.Arguments;
12+
import org.junit.jupiter.params.provider.MethodSource;
13+
14+
import java.math.BigDecimal;
15+
import java.math.BigInteger;
16+
import java.util.stream.Stream;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
20+
class NumberSerializerTest {
21+
22+
private final NumberSerializer sut = new NumberSerializer();
23+
24+
@ParameterizedTest
25+
@MethodSource("serializationData")
26+
void serializeSerializesNumberAsTypedValueNode(Number value, JsonNode expected) {
27+
assertEquals(expected, sut.serialize(value, new SerializationContext<>(value, DummyJsonLdContext.INSTANCE)));
28+
}
29+
30+
static Stream<Arguments> serializationData() {
31+
return Stream.of(
32+
Arguments.of((short) 1, typedNode((short) 1, XSD.SHORT)),
33+
Arguments.of(1, typedNode(1, XSD.INT)),
34+
Arguments.of(1L, typedNode(1L, XSD.LONG)),
35+
Arguments.of(1.0f, typedNode(1.0f, XSD.FLOAT)),
36+
Arguments.of(1.0, typedNode(1.0, XSD.DOUBLE)),
37+
Arguments.of(BigInteger.valueOf(1), typedNode(BigInteger.valueOf(1), XSD.INTEGER)),
38+
Arguments.of(BigDecimal.valueOf(1.1), typedNode(BigDecimal.valueOf(1.1), XSD.DECIMAL))
39+
);
40+
}
41+
42+
private static JsonNode typedNode(Number value, String datatype) {
43+
final ObjectNode node = JsonNodeFactory.createObjectNode();
44+
node.addItem(JsonNodeFactory.createLiteralNode(JsonLd.TYPE, datatype));
45+
node.addItem(JsonNodeFactory.createLiteralNode(JsonLd.VALUE, value));
46+
return node;
47+
}
48+
}

0 commit comments

Comments
 (0)