diff --git a/src/main/java/com/njcn/middle/cache/support/CacheValueCodec.java b/src/main/java/com/njcn/middle/cache/support/CacheValueCodec.java
new file mode 100644
index 0000000..5807c6f
--- /dev/null
+++ b/src/main/java/com/njcn/middle/cache/support/CacheValueCodec.java
@@ -0,0 +1,37 @@
+package com.njcn.middle.cache.support;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.njcn.middle.cache.constant.CacheConstant;
+
+/**
+ * 缓存值编解码:对象 ↔ 字符串(fastjson),并承载空值标记。
+ *
+ *
解码失败直接抛 fastjson 异常({@link RuntimeException}),由调用方决定显式暴露(public get)
+ * 还是当未命中降级(getOrLoad 内部)。
+ */
+public class CacheValueCodec {
+
+ /** 对象 → 字符串:null → 空值标记;否则 fastjson 序列化。 */
+ public String encode(Object value) {
+ if (value == null) {
+ return CacheConstant.NULL_SENTINEL;
+ }
+ return JSON.toJSONString(value);
+ }
+
+ /** 是否空值标记。 */
+ public boolean isNullSentinel(String raw) {
+ return CacheConstant.NULL_SENTINEL.equals(raw);
+ }
+
+ /** 字符串 → 对象(Class)。 */
+ public T decode(String raw, Class type) {
+ return JSON.parseObject(raw, type);
+ }
+
+ /** 字符串 → 对象(TypeReference,支持泛型)。 */
+ public T decode(String raw, TypeReference type) {
+ return JSON.parseObject(raw, type);
+ }
+}
diff --git a/src/test/java/com/njcn/middle/cache/support/CacheValueCodecTest.java b/src/test/java/com/njcn/middle/cache/support/CacheValueCodecTest.java
new file mode 100644
index 0000000..e37df46
--- /dev/null
+++ b/src/test/java/com/njcn/middle/cache/support/CacheValueCodecTest.java
@@ -0,0 +1,64 @@
+package com.njcn.middle.cache.support;
+
+import com.alibaba.fastjson.TypeReference;
+import com.njcn.middle.cache.constant.CacheConstant;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CacheValueCodecTest {
+
+ private final CacheValueCodec codec = new CacheValueCodec();
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class Foo {
+ private Long id;
+ private String name;
+ }
+
+ @Test
+ void encodeNullReturnsSentinel() {
+ assertEquals(CacheConstant.NULL_SENTINEL, codec.encode(null));
+ assertTrue(codec.isNullSentinel(codec.encode(null)));
+ assertFalse(codec.isNullSentinel("{\"id\":1}"));
+ }
+
+ @Test
+ void encodeDecodePojoRoundTrip() {
+ Foo foo = new Foo(1L, "alice");
+ String raw = codec.encode(foo);
+ Foo back = codec.decode(raw, Foo.class);
+ assertEquals(foo, back);
+ }
+
+ @Test
+ void encodeDecodeStringRoundTrip() {
+ String raw = codec.encode("abc");
+ assertEquals("abc", codec.decode(raw, String.class));
+ }
+
+ @Test
+ void decodeGenericWithTypeReference() {
+ List list = Arrays.asList(1, 2, 3);
+ String raw = codec.encode(list);
+ List back = codec.decode(raw, new TypeReference>() {
+ });
+ assertEquals(list, back);
+ }
+
+ @Test
+ void decodeDirtyStringThrows() {
+ assertThrows(RuntimeException.class, () -> codec.decode("{not-json", Foo.class));
+ }
+}