Skip to content

Commit 2f7270e

Browse files
author
Mikhail Samin's Claude
committed
Release v1.5.13
Make response parsing lenient: never throw because a field is missing or a different type than expected. The enterprise endpoint can return songs with no score (also missing isrc/upc/label, and some fields on YouTube-sourced entries). Those fields are now optional/nullable and degrade instead of raising. Kept legitimate failures: API status=error, undecodable JSON, transport errors, and caller-input errors (unknown provider, missing token).
1 parent ceddadf commit 2f7270e

13 files changed

Lines changed: 150 additions & 12 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.audd</groupId>
88
<artifactId>audd</artifactId>
9-
<version>1.5.12</version>
9+
<version>1.5.13</version>
1010
<packaging>jar</packaging>
1111

1212
<name>AudD Java SDK</name>

src/main/java/io/audd/AsyncAudD.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.audd.advanced.AsyncAdvanced;
66
import io.audd.customcatalog.AsyncCustomCatalog;
77
import io.audd.internal.HttpClient;
8+
import io.audd.internal.Json;
89
import io.audd.internal.ResponseDecoder;
910
import io.audd.internal.RetryClass;
1011
import io.audd.internal.RetryPolicy;
@@ -30,7 +31,7 @@
3031
* {@link AudD}; safe with try-with-resources.
3132
*/
3233
public final class AsyncAudD implements AutoCloseable {
33-
private static final ObjectMapper MAPPER = new ObjectMapper();
34+
private static final ObjectMapper MAPPER = Json.mapper();
3435

3536
private final HttpClient http;
3637
private final HttpClient enterpriseHttp;

src/main/java/io/audd/AudD.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.audd.errors.AudDConnectionError;
77
import io.audd.internal.HttpClient;
88
import io.audd.internal.HttpResponse;
9+
import io.audd.internal.Json;
910
import io.audd.internal.ResponseDecoder;
1011
import io.audd.internal.RetryClass;
1112
import io.audd.internal.RetryPolicy;
@@ -49,7 +50,7 @@ public final class AudD implements AutoCloseable {
4950
+ "Get a token at https://dashboard.audd.io and pass it as AudD.builder().apiToken(...) "
5051
+ "or set " + API_TOKEN_ENV_VAR + ".";
5152

52-
private static final ObjectMapper MAPPER = new ObjectMapper();
53+
private static final ObjectMapper MAPPER = Json.mapper();
5354

5455
private static final java.util.logging.Logger LOGGER =
5556
java.util.logging.Logger.getLogger("io.audd");

src/main/java/io/audd/advanced/Advanced.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.audd.errors.ErrorMapping;
99
import io.audd.internal.HttpClient;
1010
import io.audd.internal.HttpResponse;
11+
import io.audd.internal.Json;
1112
import io.audd.internal.RetryPolicy;
1213
import io.audd.models.LyricsResult;
1314

@@ -25,7 +26,7 @@
2526
* main client.
2627
*/
2728
public final class Advanced {
28-
private static final ObjectMapper MAPPER = new ObjectMapper();
29+
private static final ObjectMapper MAPPER = Json.mapper();
2930

3031
private final HttpClient http;
3132
private final RetryPolicy recognitionPolicy;

src/main/java/io/audd/advanced/AsyncAdvanced.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.audd.errors.AudDSerializationError;
77
import io.audd.errors.ErrorMapping;
88
import io.audd.internal.HttpClient;
9+
import io.audd.internal.Json;
910
import io.audd.internal.RetryPolicy;
1011
import io.audd.models.LyricsResult;
1112

@@ -19,7 +20,7 @@
1920

2021
/** Async advanced namespace — mirror of {@link Advanced}. */
2122
public final class AsyncAdvanced {
22-
private static final ObjectMapper MAPPER = new ObjectMapper();
23+
private static final ObjectMapper MAPPER = Json.mapper();
2324

2425
private final HttpClient http;
2526
private final RetryPolicy recognitionPolicy;

src/main/java/io/audd/internal/HttpClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public final class HttpClient implements AutoCloseable {
3131
public static final long DEFAULT_READ_TIMEOUT_S = 60;
3232
public static final long ENTERPRISE_READ_TIMEOUT_S = 3600;
3333

34-
private static final ObjectMapper MAPPER = new ObjectMapper();
34+
private static final ObjectMapper MAPPER = Json.mapper();
3535

3636
private final AtomicReference<String> apiToken;
3737
private final OkHttpClient http;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.audd.internal;
2+
3+
import com.fasterxml.jackson.databind.DeserializationFeature;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
6+
/**
7+
* Shared, leniently-configured {@link ObjectMapper} for all response parsing.
8+
*
9+
* <p>A successful API response must never fail to parse because a field is
10+
* absent or a different type than expected. The enterprise endpoint, for
11+
* example, legitimately returns matches with no {@code score} (and no
12+
* {@code isrc}/{@code upc}/{@code label}). The mapper here is configured so
13+
* that missing, unknown, and null-for-primitive fields all degrade to
14+
* sensible defaults instead of throwing.</p>
15+
*
16+
* <p>This does not weaken the error contract: a {@code status=error} body is
17+
* still turned into a typed exception before any model is decoded, and a body
18+
* that is not valid JSON at all still surfaces as a serialization error.</p>
19+
*/
20+
public final class Json {
21+
private static final ObjectMapper MAPPER = newLenientMapper();
22+
23+
private Json() {}
24+
25+
/** The shared lenient mapper. Jackson {@link ObjectMapper} is thread-safe once configured. */
26+
public static ObjectMapper mapper() {
27+
return MAPPER;
28+
}
29+
30+
/**
31+
* Build a mapper that tolerates the natural shape variation of AudD
32+
* responses:
33+
* <ul>
34+
* <li>unknown keys are ignored (typed models also absorb them via
35+
* {@code @JsonAnySetter}, but this protects any plain POJO too);</li>
36+
* <li>a JSON {@code null} for a primitive field becomes the primitive's
37+
* default rather than throwing;</li>
38+
* <li>a missing {@code @JsonCreator} property is allowed;</li>
39+
* <li>scalars accepted for single-element arrays and vice versa, so a
40+
* field that is sometimes an object and sometimes a list still
41+
* decodes.</li>
42+
* </ul>
43+
*/
44+
public static ObjectMapper newLenientMapper() {
45+
return new ObjectMapper()
46+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
47+
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false)
48+
.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
49+
.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false)
50+
.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
51+
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
52+
.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true)
53+
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)
54+
.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
55+
}
56+
}

src/main/java/io/audd/internal/UserAgent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/** SDK identifier sent on every request. */
44
public final class UserAgent {
5-
public static final String SDK_VERSION = "1.5.12";
5+
public static final String SDK_VERSION = "1.5.13";
66

77
private UserAgent() {}
88

src/main/java/io/audd/models/AppleMusicMetadata.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package io.audd.models;
22

3-
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
43
import com.fasterxml.jackson.annotation.JsonProperty;
54

6-
@JsonIgnoreProperties(ignoreUnknown = false)
75
public final class AppleMusicMetadata extends ForwardCompatible {
86
@JsonProperty("artistName") public String artistName;
97
@JsonProperty("url") public String url;

src/main/java/io/audd/streams/AsyncStreams.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.audd.errors.AudDInvalidRequestError;
77
import io.audd.errors.AudDSerializationError;
88
import io.audd.internal.HttpClient;
9+
import io.audd.internal.Json;
910
import io.audd.internal.ResponseDecoder;
1011
import io.audd.internal.RetryPolicy;
1112
import io.audd.models.CallbackEvent;
@@ -23,7 +24,7 @@
2324

2425
/** Async streams namespace. Returns {@link CompletableFuture}s. */
2526
public final class AsyncStreams {
26-
private static final ObjectMapper MAPPER = new ObjectMapper();
27+
private static final ObjectMapper MAPPER = Json.mapper();
2728
private static final int NO_CALLBACK_ERROR_CODE = 19;
2829

2930
private final HttpClient http;

0 commit comments

Comments
 (0)