Skip to content

Commit 075ec92

Browse files
authored
Fix #3063: resolve seeming @JsonValue conflict (useful with Records) (#3735)
1 parent d5cdd7b commit 075ec92

File tree

4 files changed

+93
-7
lines changed

4 files changed

+93
-7
lines changed

release-notes/CREDITS-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -1532,3 +1532,7 @@ Moritz Halbritter (mhalbritter@github)
15321532
Philippe Marschall (marschall@github)
15331533
* Contributed #3699: Allow custom `JsonNode` implementations
15341534
(2.14.2)
1535+
1536+
Gili Tzabari (cowwoc@github)
1537+
* Reported #3063: `@JsonValue` fails for Java Record
1538+
(2.14.2)

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Project: jackson-databind
88

99
#1751: `@JsonTypeInfo` does not work if the Type Id is an Integer value
1010
(reported by @marvin-we)
11+
#3063: `@JsonValue` fails for Java Record
12+
(reported by Gili T)
1113
#3699: Allow custom `JsonNode` implementations
1214
(contributed by Philippe M)
1315
#3711: Enum polymorphism not working correctly with DEDUCTION

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

+54-7
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,11 @@ public AnnotatedMember getJsonKeyAccessor() {
251251
// If @JsonKey defined, must have a single one
252252
if (_jsonKeyAccessors != null) {
253253
if (_jsonKeyAccessors.size() > 1) {
254-
reportProblem("Multiple 'as-key' properties defined (%s vs %s)",
255-
_jsonKeyAccessors.get(0),
256-
_jsonKeyAccessors.get(1));
254+
if (!_resolveFieldVsGetter(_jsonKeyAccessors)) {
255+
reportProblem("Multiple 'as-key' properties defined (%s vs %s)",
256+
_jsonKeyAccessors.get(0),
257+
_jsonKeyAccessors.get(1));
258+
}
257259
}
258260
// otherwise we won't greatly care
259261
return _jsonKeyAccessors.get(0);
@@ -270,11 +272,14 @@ public AnnotatedMember getJsonValueAccessor()
270272
collectAll();
271273
}
272274
// If @JsonValue defined, must have a single one
275+
// 15-Jan-2023, tatu: Except let's try resolving "getter-over-field" case at least
273276
if (_jsonValueAccessors != null) {
274277
if (_jsonValueAccessors.size() > 1) {
275-
reportProblem("Multiple 'as-value' properties defined (%s vs %s)",
276-
_jsonValueAccessors.get(0),
277-
_jsonValueAccessors.get(1));
278+
if (!_resolveFieldVsGetter(_jsonValueAccessors)) {
279+
reportProblem("Multiple 'as-value' properties defined (%s vs %s)",
280+
_jsonValueAccessors.get(0),
281+
_jsonValueAccessors.get(1));
282+
}
278283
}
279284
// otherwise we won't greatly care
280285
return _jsonValueAccessors.get(0);
@@ -1123,7 +1128,7 @@ protected void _renameWithWrappers(Map<String, POJOPropertyBuilder> props)
11231128

11241129
/*
11251130
/**********************************************************
1126-
/* Overridable internal methods, sorting, other stuff
1131+
/* Internal methods, sorting
11271132
/**********************************************************
11281133
*/
11291134

@@ -1244,6 +1249,48 @@ private boolean _anyIndexed(Collection<POJOPropertyBuilder> props) {
12441249
return false;
12451250
}
12461251

1252+
/*
1253+
/**********************************************************
1254+
/* Internal methods, conflict resolution
1255+
/**********************************************************
1256+
*/
1257+
1258+
/**
1259+
* Method that will be given a {@link List} with 2 or more accessors
1260+
* that may be in conflict: it will need to remove lower-priority accessors
1261+
* to leave just a single highest-priority accessor to use.
1262+
* If this succeeds method returns {@code true}, otherwise {@code false}.
1263+
*<p>
1264+
* NOTE: method will directly modify given {@code List} directly, regardless
1265+
* of whether it ultimately succeeds or not.
1266+
*
1267+
* @return True if seeming conflict was resolved and there only remains
1268+
* single accessor
1269+
*/
1270+
protected boolean _resolveFieldVsGetter(List<AnnotatedMember> accessors) {
1271+
do {
1272+
AnnotatedMember acc1 = accessors.get(0);
1273+
AnnotatedMember acc2 = accessors.get(1);
1274+
1275+
if (acc1 instanceof AnnotatedField) {
1276+
if (acc2 instanceof AnnotatedMethod) {
1277+
// Method has precedence, remove first entry
1278+
accessors.remove(0);
1279+
continue;
1280+
}
1281+
} else if (acc1 instanceof AnnotatedMethod) {
1282+
// Method has precedence, remove second entry
1283+
if (acc2 instanceof AnnotatedField) {
1284+
accessors.remove(1);
1285+
continue;
1286+
}
1287+
}
1288+
// Not a field/method pair; fail
1289+
return false;
1290+
} while (accessors.size() > 1);
1291+
return true;
1292+
}
1293+
12471294
/*
12481295
/**********************************************************
12491296
/* Internal methods; helpers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.fasterxml.jackson.databind.records;
2+
3+
import java.util.Collections;
4+
import java.util.Map;
5+
6+
import com.fasterxml.jackson.annotation.*;
7+
8+
import com.fasterxml.jackson.databind.BaseMapTest;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
11+
public class RecordJsonValue3063Test extends BaseMapTest
12+
{
13+
// [databind#3063]
14+
record GetLocations3063(@JsonValue Map<String, String> nameToLocation)
15+
{
16+
@JsonCreator
17+
public GetLocations3063(Map<String, String> nameToLocation)
18+
{
19+
this.nameToLocation = nameToLocation;
20+
}
21+
}
22+
23+
private final ObjectMapper MAPPER = newJsonMapper();
24+
25+
// [databind#3063]
26+
public void testRecordWithJsonValue3063() throws Exception
27+
{
28+
Map<String, String> locations = Collections.singletonMap("a", "locationA");
29+
String json = MAPPER.writeValueAsString(new GetLocations3063(locations));
30+
31+
assertNotNull(json);
32+
}
33+
}

0 commit comments

Comments
 (0)