PortfolioJsonCodec.java

package com.taxonomy.portfolio.service;

import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/** Central JSON codec for immutable snapshot payloads and extension attributes. */
@Service
public class PortfolioJsonCodec {

    private final ObjectMapper objectMapper;

    public PortfolioJsonCodec(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    public String write(Object value) {
        if (value == null) return null;
        try {
            return objectMapper.writeValueAsString(value);
        } catch (Exception exception) {
            throw PortfolioException.analysisFailed("Could not serialize portfolio payload", exception);
        }
    }

    public <T> T read(String json, Class<T> type) {
        if (json == null || json.isBlank()) return null;
        try {
            return objectMapper.readValue(json, type);
        } catch (Exception exception) {
            throw PortfolioException.analysisFailed(
                    "Could not deserialize portfolio payload as " + type.getSimpleName(), exception);
        }
    }

    public List<Long> readLongList(String json) {
        if (json == null || json.isBlank()) return List.of();
        try {
            return List.copyOf(objectMapper.readValue(json, new TypeReference<List<Long>>() {}));
        } catch (Exception exception) {
            throw PortfolioException.analysisFailed("Could not deserialize source fragment IDs", exception);
        }
    }

    public Map<String, String> readStringMap(String json) {
        if (json == null || json.isBlank()) return Map.of();
        try {
            Map<String, String> values = objectMapper.readValue(
                    json, new TypeReference<Map<String, String>>() {});
            return Map.copyOf(new LinkedHashMap<>(values));
        } catch (Exception exception) {
            throw PortfolioException.analysisFailed("Could not deserialize extension attributes", exception);
        }
    }
}