Skip to content

Commit aed3a7a

Browse files
Merge pull request #230 from rosette-api/rlpnc-7501-record-similarity-left-and-right-correct-deserialization
Rlpnc 7501 record similarity left and right objects: correct deserialization
2 parents 81cb036 + d8a4c05 commit aed3a7a

File tree

10 files changed

+257
-64
lines changed

10 files changed

+257
-64
lines changed

json/src/main/java/com/basistech/rosette/apimodel/jackson/ApiModelMixinModule.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import com.basistech.rosette.apimodel.jackson.recordsimilaritydeserializers.AddressFieldDeserializer;
2828
import com.basistech.rosette.apimodel.jackson.recordsimilaritydeserializers.DateFieldDeserializer;
2929
import com.basistech.rosette.apimodel.jackson.recordsimilaritydeserializers.NameFieldDeserializer;
30-
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityResult;
31-
import com.basistech.rosette.apimodel.jackson.recordsimilaritydeserializers.RecordSimilarityResultDeserializer;
30+
import com.basistech.rosette.apimodel.jackson.recordsimilaritydeserializers.RecordSimilarityResponseDeserializer;
31+
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityResponse;
3232
import com.basistech.rosette.apimodel.recordsimilarity.records.AddressField;
3333
import com.basistech.rosette.apimodel.recordsimilarity.records.DateField;
3434
import com.basistech.rosette.apimodel.recordsimilarity.records.NameField;
@@ -98,7 +98,7 @@ public void setupModule(Module.SetupContext context) {
9898
deserializers.addDeserializer(DateField.class, new DateFieldDeserializer());
9999
deserializers.addDeserializer(AddressField.class, new AddressFieldDeserializer());
100100
deserializers.addDeserializer(RecordSimilarityRequest.class, new RecordSimilarityRequestDeserializer());
101-
deserializers.addDeserializer(RecordSimilarityResult.class, new RecordSimilarityResultDeserializer());
101+
deserializers.addDeserializer(RecordSimilarityResponse.class, new RecordSimilarityResponseDeserializer());
102102
context.addDeserializers(deserializers);
103103
}
104104

json/src/main/java/com/basistech/rosette/apimodel/jackson/recordsimilaritydeserializers/RecordSimilarityDeserializerUtilities.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@
2020
import java.util.HashMap;
2121
import java.util.Iterator;
2222
import java.util.Map;
23+
import java.util.Optional;
2324

25+
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityExplainInfo;
26+
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityResult;
2427
import com.fasterxml.jackson.core.JsonParser;
2528
import com.fasterxml.jackson.databind.JsonNode;
29+
2630
import javax.validation.Valid;
31+
import javax.validation.constraints.NotNull;
2732

2833
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityFieldInfo;
2934
import com.basistech.rosette.apimodel.recordsimilarity.records.AddressField;
@@ -33,9 +38,41 @@
3338

3439
final class RecordSimilarityDeserializerUtilities {
3540

36-
private RecordSimilarityDeserializerUtilities() { }
41+
private RecordSimilarityDeserializerUtilities() {
42+
}
43+
44+
public static RecordSimilarityResult parseResult(
45+
JsonNode node,
46+
JsonParser jsonParser,
47+
@Valid Map<String, RecordSimilarityFieldInfo> fields
48+
) throws IOException {
49+
final Double score = node.get("score") != null
50+
? node.get("score").traverse(jsonParser.getCodec()).readValueAs(Double.class)
51+
: null;
52+
final RecordSimilarityExplainInfo explainInfo = node.get("explainInfo") != null
53+
? node.get("explainInfo").traverse(jsonParser.getCodec()).readValueAs(RecordSimilarityExplainInfo.class)
54+
: null;
55+
final Map<String, RecordSimilarityField> left = node.get("left") != null && fields != null
56+
? parseRecord(node.get("left"), jsonParser, fields)
57+
: null;
58+
final Map<String, RecordSimilarityField> right = node.get("right") != null && fields != null
59+
? parseRecord(node.get("right"), jsonParser, fields)
60+
: null;
61+
final String error = Optional.ofNullable(node.get("error")).map(JsonNode::asText).orElse(null);
62+
return RecordSimilarityResult.builder()
63+
.score(score)
64+
.left(left)
65+
.right(right)
66+
.explainInfo(explainInfo)
67+
.error(error)
68+
.build();
69+
}
3770

38-
static Map<String, RecordSimilarityField> parseRecord(JsonNode jsonNode, @Valid Map<String, RecordSimilarityFieldInfo> fields, JsonParser jsonParser) throws IOException {
71+
static Map<String, RecordSimilarityField> parseRecord(
72+
JsonNode jsonNode,
73+
JsonParser jsonParser,
74+
@NotNull @Valid Map<String, RecordSimilarityFieldInfo> fields
75+
) throws IOException {
3976
final Iterator<Map.Entry<String, JsonNode>> recordsIterator = jsonNode.fields();
4077
final Map<String, RecordSimilarityField> recordMap = new HashMap<>();
4178
while (recordsIterator.hasNext()) {

json/src/main/java/com/basistech/rosette/apimodel/jackson/recordsimilaritydeserializers/RecordSimilarityRequestDeserializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private static List<Map<String, RecordSimilarityField>> parseRecords(final JsonN
6767
final JsonParser jsonParser) throws IOException {
6868
final List<Map<String, RecordSimilarityField>> records = new ArrayList<>();
6969
for (JsonNode recordNode : arrayNode) {
70-
records.add(RecordSimilarityDeserializerUtilities.parseRecord(recordNode, fields, jsonParser));
70+
records.add(RecordSimilarityDeserializerUtilities.parseRecord(recordNode, jsonParser, fields));
7171
}
7272
return records;
7373
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2024 Basis Technology Corp.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.basistech.rosette.apimodel.jackson.recordsimilaritydeserializers;
18+
19+
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityFieldInfo;
20+
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityResponse;
21+
import com.basistech.rosette.apimodel.recordsimilarity.RecordSimilarityResult;
22+
import com.fasterxml.jackson.core.JsonParser;
23+
import com.fasterxml.jackson.core.type.TypeReference;
24+
import com.fasterxml.jackson.databind.DeserializationContext;
25+
import com.fasterxml.jackson.databind.JsonNode;
26+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
27+
28+
import java.io.IOException;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
import java.util.Map;
32+
import java.util.Optional;
33+
34+
public class RecordSimilarityResponseDeserializer extends StdDeserializer<RecordSimilarityResponse> {
35+
36+
private static final TypeReference<Map<String, RecordSimilarityFieldInfo>> FIELDS_TYPE_REFERENCE = new TypeReference<>() {
37+
};
38+
39+
public RecordSimilarityResponseDeserializer() {
40+
super(RecordSimilarityResponse.class);
41+
}
42+
43+
@Override
44+
public RecordSimilarityResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
45+
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
46+
47+
JsonNode fieldsNode = node.get("fields");
48+
49+
Map<String, RecordSimilarityFieldInfo> fields = fieldsNode != null ? node.get("fields").traverse(jsonParser.getCodec()).readValueAs(FIELDS_TYPE_REFERENCE) : null;
50+
String errorMessage = Optional.ofNullable(node.get("errorMessage")).map(JsonNode::asText).orElse(null);
51+
52+
JsonNode resultsNode = node.get("results");
53+
List<RecordSimilarityResult> results = new ArrayList<>();
54+
if (resultsNode != null) {
55+
for (JsonNode resultNode : resultsNode) {
56+
results.add(RecordSimilarityDeserializerUtilities.parseResult(resultNode, jsonParser, fields));
57+
}
58+
}
59+
60+
return RecordSimilarityResponse.builder()
61+
.fields(fields)
62+
.results(results)
63+
.errorMessage(errorMessage)
64+
.build();
65+
}
66+
}

json/src/main/java/com/basistech/rosette/apimodel/jackson/recordsimilaritydeserializers/RecordSimilarityResultDeserializer.java

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2024 Basis Technology Corp.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.basistech.rosette.apimodel.recordsimilarity;
17+
18+
19+
import com.basistech.rosette.apimodel.jackson.ApiModelMixinModule;
20+
import com.basistech.rosette.apimodel.recordsimilarity.records.AddressField;
21+
import com.basistech.rosette.apimodel.recordsimilarity.records.DateField;
22+
import com.basistech.rosette.apimodel.recordsimilarity.records.NameField;
23+
import com.basistech.rosette.apimodel.recordsimilarity.records.RecordFieldType;
24+
import com.basistech.util.ISO15924;
25+
import com.basistech.util.LanguageCode;
26+
import com.basistech.util.NEConstants;
27+
import com.fasterxml.jackson.core.JsonProcessingException;
28+
import com.fasterxml.jackson.databind.MapperFeature;
29+
import com.fasterxml.jackson.databind.ObjectMapper;
30+
import com.fasterxml.jackson.databind.SerializationFeature;
31+
import org.junit.jupiter.api.Test;
32+
33+
import java.util.List;
34+
import java.util.Map;
35+
36+
import static org.junit.jupiter.api.Assertions.assertEquals;
37+
38+
public class RecordSimilarityResponseTest {
39+
40+
private static final ObjectMapper MAPPER = ApiModelMixinModule.setupObjectMapper(new ObjectMapper());
41+
42+
private static final String EXPECTED_JSON = "{\"fields\":{\"addr\":{\"type\":\"rni_address\",\"weight\":0.3},\"dob\":{\"type\":\"rni_date\",\"weight\":0.2},\"primaryName\":{\"type\":\"rni_name\",\"weight\":0.5}},\"results\":[{\"explainInfo\":{\"leftOnlyFields\":[\"addr\"],\"scoredFields\":{\"dob\":{\"calculatedWeight\":0.2857142857142857,\"finalScore\":0.74,\"rawScore\":0.8,\"weight\":0.5},\"primaryName\":{\"calculatedWeight\":0.7142857142857143,\"details\":\"any details\",\"finalScore\":0.85,\"rawScore\":0.99,\"weight\":0.5}}},\"left\":{\"addr\":{\"address\":\"123 Roadlane Ave\"},\"dob\":{\"date\":\"1993-04-16\"},\"primaryName\":{\"entityType\":\"PERSON\",\"language\":\"eng\",\"languageOfOrigin\":\"eng\",\"script\":\"Latn\",\"text\":\"Ethan R\"}},\"right\":{\"dob\":\"1993-04-16\",\"primaryName\":{\"text\":\"Seth R\"}},\"score\":0.87},{\"error\":\"Field foo not found in field mapping\",\"left\":{\"addr\":{\"address\":\"123 Roadlane Ave\"},\"dob\":{\"date\":\"1993-04-16\"},\"primaryName\":{\"entityType\":\"PERSON\",\"language\":\"eng\",\"languageOfOrigin\":\"eng\",\"script\":\"Latn\",\"text\":\"Ethan R\"}},\"right\":{\"dob\":\"1993-04-16\",\"primaryName\":{\"text\":\"Seth R\"}}}]}";
43+
44+
private static final RecordSimilarityResponse EXPECTED_RESPONSE;
45+
46+
static {
47+
RecordSimilarityResponse temp;
48+
try {
49+
temp = RecordSimilarityResponse.builder()
50+
.fields(Map.of("primaryName", RecordSimilarityFieldInfo.builder()
51+
.type(RecordFieldType.NAME)
52+
.weight(0.5)
53+
.build(),
54+
"dob", RecordSimilarityFieldInfo.builder()
55+
.type(RecordFieldType.DATE)
56+
.weight(0.2)
57+
.build(),
58+
"addr", RecordSimilarityFieldInfo.builder()
59+
.type(RecordFieldType.ADDRESS)
60+
.weight(0.3)
61+
.build()))
62+
.results(List.of(RecordSimilarityResult.builder()
63+
.score(0.87)
64+
.left(Map.of("primaryName", NameField.FieldedName.builder()
65+
.text("Ethan R")
66+
.language(LanguageCode.ENGLISH)
67+
.entityType(NEConstants.toString(NEConstants.NE_TYPE_PERSON))
68+
.languageOfOrigin(LanguageCode.ENGLISH)
69+
.script(ISO15924.Latn)
70+
.build(),
71+
"dob", DateField.FieldedDate.builder()
72+
.date("1993-04-16")
73+
.build(),
74+
"addr", AddressField.FieldedAddress.builder()
75+
.address("123 Roadlane Ave")
76+
.build()))
77+
.right(Map.of("primaryName", NameField.FieldedName.builder()
78+
.text("Seth R")
79+
.build(),
80+
"dob", DateField.UnfieldedDate.builder()
81+
.date("1993-04-16")
82+
.build()))
83+
.explainInfo(RecordSimilarityExplainInfo.builder()
84+
.leftOnlyFields(List.of("addr"))
85+
.scoredFields(Map.of("dob", RecordSimilarityFieldExplainInfo.builder()
86+
.weight(0.5)
87+
.calculatedWeight(0.2857142857142857)
88+
.rawScore(0.8)
89+
.finalScore(0.74)
90+
.build(),
91+
"primaryName",
92+
RecordSimilarityFieldExplainInfo.builder()
93+
.weight(0.5)
94+
.calculatedWeight(0.7142857142857143)
95+
.rawScore(0.99)
96+
.finalScore(0.85)
97+
.details(MAPPER.readTree("\"any details\""))
98+
.build()
99+
))
100+
.build())
101+
.build(),
102+
RecordSimilarityResult.builder()
103+
.left(Map.of("primaryName", NameField.FieldedName.builder()
104+
.text("Ethan R")
105+
.language(LanguageCode.ENGLISH)
106+
.entityType(NEConstants.toString(NEConstants.NE_TYPE_PERSON))
107+
.languageOfOrigin(LanguageCode.ENGLISH)
108+
.script(ISO15924.Latn)
109+
.build(),
110+
"dob", DateField.FieldedDate.builder()
111+
.date("1993-04-16")
112+
.build(),
113+
"addr", AddressField.FieldedAddress.builder()
114+
.address("123 Roadlane Ave")
115+
.build()))
116+
.right(Map.of("primaryName", NameField.FieldedName.builder()
117+
.text("Seth R")
118+
.build(),
119+
"dob", DateField.UnfieldedDate.builder()
120+
.date("1993-04-16")
121+
.build()))
122+
.error("Field foo not found in field mapping")
123+
.build()))
124+
.build();
125+
} catch (JsonProcessingException e) {
126+
temp = RecordSimilarityResponse.builder().build();
127+
}
128+
EXPECTED_RESPONSE = temp;
129+
}
130+
131+
@Test
132+
public void testDeserialization() throws JsonProcessingException {
133+
final RecordSimilarityResponse response = MAPPER.readValue(EXPECTED_JSON, RecordSimilarityResponse.class);
134+
assertEquals(EXPECTED_RESPONSE, response);
135+
}
136+
137+
@Test
138+
public void testSerialization() throws JsonProcessingException {
139+
// For testing, force ordering
140+
MAPPER.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
141+
MAPPER.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
142+
assertEquals(EXPECTED_JSON, MAPPER.writeValueAsString(EXPECTED_RESPONSE));
143+
}
144+
}

0 commit comments

Comments
 (0)