/
JSONLDSerializer.groovy
162 lines (144 loc) · 5.54 KB
/
JSONLDSerializer.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package se.lagrummet.rinfo.base.rdf.jsonld
import org.openrdf.repository.Repository
import se.lagrummet.rinfo.base.rdf.Describer
import se.lagrummet.rinfo.base.rdf.Description
import se.lagrummet.rinfo.base.rdf.RDFLiteral
class JSONLDSerializer {
static final RDF_TYPE = Describer.RDF_NS + "type"
static final XSD = Describer.XSD_NS
JSONLDContext context
boolean keepUnmapped
boolean addRevs
JSONLDSerializer(context, keepUnmapped=false, addRevs=false) {
this.context = context
this.keepUnmapped = keepUnmapped
this.addRevs = addRevs
}
Map toJSON(Repository repo, String resourceIri) {
def describer = new Describer(repo.getConnection())
def item = null
try {
def itemPath = new HashSet<String>()
def description = describer.findDescription(resourceIri)
if (description != null) {
item = createJSON(description, itemPath, null)
} else {
item = [:]
addRevsToItem(item, resourceIri, describer, itemPath, null)
}
} finally {
describer.close()
}
return item.size() > 0? item : null
}
Map createJSON(Description description, Set itemPath, String viaKey) {
def item = [:]
def about = description.about
if (about != null && !about.startsWith("_:")) {
item[context.idKey] = toKey(about, true)
}
if (!itemPath.contains(about)) {
itemPath << about
description.propertyValuesMap.each { prop, values ->
if (prop == RDF_TYPE) {
def typeTokens = values.collect { toKey(it) }.findAll { it }
if (typeTokens) {
item[context.typeKey] = reduceValues(context.typeKey, typeTokens)
}
// NOTE: we don't stop for type here, since you might want
// *both* a type token and data about type...
}
def key = toKey(prop)
if (!key) {
return
}
def result = values.collect {
valueToJSON(description.describer, key, it, itemPath)
}
boolean asSet = context.keyTermMap[key]?.isSet
item[key] = asSet? result : reduceValues(key, result)
}
addRevsToItem(item, about, description.describer, itemPath, viaKey)
}
return item
}
String toKey(String iri) {
return toKey(iri, this.keepUnmapped)
}
String toKey(String iri, boolean keepUnmapped) {
def key = context.toKey(iri)
return (key != null)? key : keepUnmapped? iri : null
}
Object valueToJSON(Describer describer, String key, Object value, Set itemPath) {
// TODO: improve coerce mechanics (support @iri, @list...)
def term = context.keyTermMap[key]
if (value instanceof RDFLiteral) {
return toJSONLiteral(value, term?.datatype)
} else {
return createJSON(describer.newDescription(value), itemPath, key)
}
}
Object toJSONLiteral(RDFLiteral value, String coerceDatatype) {
def dt = value.datatype
if (dt == null) { // TODO: and coerceDatatype == null
if (value.lang == null || value.lang == context.lang) {
return value.toString()
} else {
return [(context.VALUE_KEY): value.toString(),
(context.LANG_KEY): value.lang]
}
}
def isXsdVocab = dt.startsWith(XSD)
def isBool = (dt == XSD + 'boolean')
// TODO: which number types? Automatic only if lexical meets canonical...
def isNumber = (!isBool && isXsdVocab && dt.substring(XSD.size()) in
['decimal', 'short', 'int', 'integer', 'float', 'double'])
if (dt == coerceDatatype) {
return (isBool || isNumber)? value.toNativeValue() : value.toString()
} else if (isBool || isNumber) {
return value.toNativeValue()
} else {
return [(context.VALUE_KEY): value.toString(),
(context.TYPE_KEY): toKey(dt)]
}
}
Object reduceValues(String key, List values) {
if (values.size() == 1) {
return values[0]
} else {
return values
}
}
void addRevsToItem(Map item, String resourceIri, Describer describer, Set itemPath, String viaKey) {
if (!addRevs)
return
def revItems = getRevData(describer, resourceIri, itemPath, viaKey)
if (revItems) {
item[context.revKey] = revItems
}
}
Map getRevData(Describer describer, String resourceIri, Set itemPath, String viaKey) {
def revItems = [:]
def revTriples = describer.triples(null, null, resourceIri)
for (triple in revTriples) {
def key = toKey(triple.property)
if (!key || key == viaKey) {
continue
}
def items = []
def revItemPath = itemPath.clone()
if (!revItemPath.contains(triple.subject)) {
def item = createJSON(describer.newDescription(triple.subject), revItemPath, key)
items << item
}
if (items) {
if (key in revItems) {
revItems[key].addAll(items)
} else {
revItems[key] = items
}
}
}
return revItems
}
}