Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

package io.agentscope.core.util;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
Expand All @@ -33,6 +35,9 @@
* Jackson's ObjectMapper with the following configuration:
* <ul>
* <li>{@code FAIL_ON_UNKNOWN_PROPERTIES = false} - allows unknown fields in JSON</li>
* <li>{@code StreamReadConstraints.maxStringLength = Integer.MAX_VALUE} - reads back any
* string value {@link #toJson} can emit; the other read constraints keep their
* Jackson defaults</li>
* </ul>
*
* <p>Users can access the underlying ObjectMapper via {@link #getObjectMapper()}
Expand Down Expand Up @@ -69,7 +74,20 @@ public JacksonJsonCodec(ObjectMapper objectMapper) {
* @return configured ObjectMapper
*/
private static ObjectMapper createDefaultObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
// toJson puts no cap on string values, yet Jackson's default read constraints reject
// any single string above 20,000,000 characters. Every AgentStateStore persists
// through this codec and inline Base64 media routinely exceeds that, so the default
// turned a successful save into a state that could never be loaded again (#3465).
// The reader must accept whatever the writer emitted; nesting depth, number and name
// length keep their defaults.
JsonFactory factory =
JsonFactory.builder()
.streamReadConstraints(
StreamReadConstraints.builder()
.maxStringLength(Integer.MAX_VALUE)
.build())
.build();
ObjectMapper mapper = new ObjectMapper(factory);
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,20 @@ void testRoundTripSerializationNested() {
assertEquals(original.author.age, deserialized.author.age);
assertEquals(original.tags, deserialized.tags);
}

@Test
void roundTripsStringsLongerThanJacksonDefaultReadLimit() {
// Jackson's default StreamReadConstraints reject any single string value above
// 20,000,000 characters on read, while toJson imposes no cap. Every AgentStateStore
// persists through this codec and inline Base64 media routinely exceeds that, so a
// state that saved successfully must stay loadable: what the codec writes, it reads.
String value = "a".repeat(20_000_001);
SimpleModel original = new SimpleModel(value, 1);

String json = codec.toJson(original);
SimpleModel deserialized = codec.fromJson(json, SimpleModel.class);

assertEquals(value.length(), deserialized.name.length());
assertEquals(value, deserialized.name);
}
}
Loading