Skip to content

Commit 92e9101

Browse files
authored
Reduce number of hashes in JSON-LD output (#905)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 64d53c9 commit 92e9101

1 file changed

Lines changed: 42 additions & 47 deletions

File tree

src/output/output_jsonld.cc

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,27 @@
44
#include <sourcemeta/core/jsonpointer.h>
55
#include <sourcemeta/core/uri.h>
66

7-
#include <algorithm> // std::ranges::sort, std::ranges::any_of
8-
#include <functional> // std::ref
9-
#include <tuple> // std::tie
10-
#include <unordered_map> // std::unordered_map
11-
#include <utility> // std::move
12-
#include <variant> // std::variant, std::get, std::holds_alternative
13-
#include <vector> // std::vector
7+
#include <algorithm> // std::ranges::sort, std::ranges::any_of
8+
#include <functional> // std::ref
9+
#include <tuple> // std::tie
10+
#include <utility> // std::move
11+
#include <variant> // std::variant, std::get, std::holds_alternative
12+
#include <vector> // std::vector
1413

1514
namespace {
1615

17-
// The JSON-LD facts accumulated at one instance location, before a descriptor
18-
// kind is derived from the instance value shape.
19-
struct Facts {
20-
std::vector<sourcemeta::core::JSONLDEdge> edges;
21-
std::vector<sourcemeta::core::JSON::String> types;
22-
};
23-
24-
using Accumulator = std::unordered_map<sourcemeta::core::WeakPointer, Facts,
25-
sourcemeta::core::WeakPointer::Hasher>;
26-
2716
// Default the undescribed elements of a collection: a scalar becomes a native
2817
// literal, and a nested array becomes an inner (unordered) collection, whose
2918
// own elements are filled the same way. As unordered collections, nested arrays
30-
// flatten into their parent during materialization. Elements that carry their
31-
// own annotations already have an accumulator entry and are left untouched.
19+
// flatten into their parent during materialization. Elements that already carry
20+
// a descriptor are left untouched.
3221
auto fill_collection(sourcemeta::core::JSONLDWeakAnnotationMap &map,
33-
const Accumulator &accumulator,
3422
const sourcemeta::core::WeakPointer &pointer,
3523
const sourcemeta::core::JSON &array) -> void {
3624
for (std::size_t index = 0; index < array.size(); index += 1) {
3725
auto element_pointer{pointer};
3826
element_pointer.push_back(index);
39-
if (accumulator.contains(element_pointer)) {
27+
if (map.contains(element_pointer)) {
4028
continue;
4129
}
4230

@@ -46,7 +34,7 @@ auto fill_collection(sourcemeta::core::JSONLDWeakAnnotationMap &map,
4634
element_pointer,
4735
sourcemeta::core::JSONLDDescriptor{
4836
.edges = {}, .value = sourcemeta::core::JSONLDCollection{}});
49-
fill_collection(map, accumulator, element_pointer, element);
37+
fill_collection(map, element_pointer, element);
5038
} else if (!element.is_object()) {
5139
map.emplace(std::move(element_pointer),
5240
sourcemeta::core::JSONLDDescriptor{
@@ -99,7 +87,12 @@ auto resolve(const sourcemeta::core::JSON &instance,
9987
const sourcemeta::blaze::SimpleOutput &output)
10088
-> std::variant<sourcemeta::core::JSONLDWeakAnnotationMap,
10189
sourcemeta::blaze::JSONLDResolutionError> {
102-
Accumulator accumulator;
90+
// Accumulate straight into the descriptor map so that each instance location
91+
// is hashed once, rather than once into a scratch map and again into the
92+
// output. Edges and provisional types land on the default node descriptor;
93+
// the second pass settles the descriptor kind once the value shape is known.
94+
sourcemeta::core::JSONLDWeakAnnotationMap map;
95+
map.reserve(output.annotations().size());
10396

10497
for (const auto &[location, values] : output.annotations()) {
10598
if (location.evaluate_path.empty()) {
@@ -108,13 +101,13 @@ auto resolve(const sourcemeta::core::JSON &instance,
108101

109102
const auto &keyword{location.evaluate_path.back().to_property()};
110103
// Only the keywords resolved below may contribute facts. An unhandled
111-
// keyword must not create an accumulator entry, as the empty facts would
112-
// otherwise materialize a spurious node or literal descriptor
104+
// keyword must not create a map entry, as the empty facts would otherwise
105+
// materialize a spurious node or literal descriptor
113106
if (keyword != "x-jsonld-id" && keyword != "x-jsonld-type") {
114107
continue;
115108
}
116109

117-
auto &facts{accumulator[location.instance_location]};
110+
auto &descriptor{map[location.instance_location]};
118111
if (keyword == "x-jsonld-id") {
119112
for (const auto &value : values) {
120113
if (!is_iri_value(value)) {
@@ -125,43 +118,49 @@ auto resolve(const sourcemeta::core::JSON &instance,
125118
.message = "The value of x-jsonld-id must be an absolute IRI"};
126119
}
127120

128-
add_edge(facts.edges, value.to_string(), false);
121+
add_edge(descriptor.edges, value.to_string(), false);
129122
}
130123
} else if (keyword == "x-jsonld-type") {
124+
auto &types{
125+
std::get<sourcemeta::core::JSONLDNode>(descriptor.value).types};
131126
for (const auto &value : values) {
132127
if (value.is_array()) {
133128
for (const auto &element : value.as_array()) {
134129
if (!is_iri_value(element)) {
135130
return type_iri_error(location.instance_location);
136131
}
137132

138-
add_type(facts.types, element.to_string());
133+
add_type(types, element.to_string());
139134
}
140135
} else {
141136
if (!is_iri_value(value)) {
142137
return type_iri_error(location.instance_location);
143138
}
144139

145-
add_type(facts.types, value.to_string());
140+
add_type(types, value.to_string());
146141
}
147142
}
148143
}
149144
}
150145

151-
sourcemeta::core::JSONLDWeakAnnotationMap map;
152-
for (auto &[pointer, facts] : accumulator) {
153-
std::ranges::sort(facts.edges,
146+
// Settle each descriptor in place, so no location is hashed a second time,
147+
// and note the array locations whose undescribed elements must be filled once
148+
// the map is no longer being iterated.
149+
std::vector<sourcemeta::core::WeakPointer> collections;
150+
for (auto &[pointer, descriptor] : map) {
151+
std::ranges::sort(descriptor.edges,
154152
[](const sourcemeta::core::JSONLDEdge &left,
155153
const sourcemeta::core::JSONLDEdge &right) -> bool {
156154
return std::tie(left.predicate, left.reverse) <
157155
std::tie(right.predicate, right.reverse);
158156
});
159-
std::ranges::sort(facts.types);
157+
auto &types{std::get<sourcemeta::core::JSONLDNode>(descriptor.value).types};
158+
std::ranges::sort(types);
160159

161160
const auto &value{sourcemeta::core::get(instance, pointer)};
162161

163162
// A type denotes an rdf:type, which only a node can carry
164-
if (!value.is_object() && !facts.types.empty()) {
163+
if (!value.is_object() && !types.empty()) {
165164
return sourcemeta::blaze::JSONLDResolutionError{
166165
.instance_location = sourcemeta::core::to_pointer(pointer),
167166
.facet = sourcemeta::blaze::JSONLDFacet::Type,
@@ -173,7 +172,7 @@ auto resolve(const sourcemeta::core::JSON &instance,
173172
// an array element (an index token) inherits the enclosing edge, so a
174173
// predicate on either is an error. A type on such a position is fine when
175174
// it is a node.
176-
if (!facts.edges.empty() &&
175+
if (!descriptor.edges.empty() &&
177176
(pointer.empty() || !pointer.back().is_property())) {
178177
return sourcemeta::blaze::JSONLDResolutionError{
179178
.instance_location = sourcemeta::core::to_pointer(pointer),
@@ -185,22 +184,18 @@ auto resolve(const sourcemeta::core::JSON &instance,
185184
"element"};
186185
}
187186

188-
sourcemeta::core::JSONLDDescriptor descriptor;
189-
descriptor.edges = std::move(facts.edges);
190-
if (value.is_object()) {
191-
descriptor.value =
192-
sourcemeta::core::JSONLDNode{.types = std::move(facts.types)};
193-
} else if (value.is_array()) {
187+
// The descriptor already carries a node with the collected types; only the
188+
// non-object shapes need their kind corrected.
189+
if (value.is_array()) {
194190
descriptor.value = sourcemeta::core::JSONLDCollection{};
195-
} else {
191+
collections.push_back(pointer);
192+
} else if (!value.is_object()) {
196193
descriptor.value = sourcemeta::core::JSONLDLiteral{};
197194
}
195+
}
198196

199-
map.emplace(pointer, std::move(descriptor));
200-
201-
if (value.is_array()) {
202-
fill_collection(map, accumulator, pointer, value);
203-
}
197+
for (const auto &pointer : collections) {
198+
fill_collection(map, pointer, sourcemeta::core::get(instance, pointer));
204199
}
205200

206201
return map;

0 commit comments

Comments
 (0)