如何创建一个确定性的Jackson ObjectMapper?



我希望能够跨JVM生成任何Java POJO的MD5校验和。方法是将对象序列化为 JSON,然后将 MD5 序列化为 JSON。

问题是 Jackson 的 JSON 序列化不是确定性的,主要是因为许多集合不是确定性的。

ObjectMapper mapper = new ObjectMapper()                                               
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)                                           
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
... // all other custom modules / features
;

这两个功能解决了在POJO和地图上保持字段排序的两个问题。

下一个挑战是动态修改任何集合并对其进行排序。这要求每个集合中的每个元素都是可排序的,但让我们假设现在可以了。

有没有办法截获每个集合并在序列化之前对其进行排序?

我用下面的代码实现了这个目标。阅读更多关于创建有点确定性的 Jackson ObjectMapper 的信息

public class DeterministicObjectMapper {
private DeterministicObjectMapper() { }
public static ObjectMapper create(ObjectMapper original, CustomComparators customComparators) {
ObjectMapper mapper = original.copy()
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
/*
*  Get the original instance of the SerializerProvider before we add our custom module.
*  Our Collection Delegating code does not call itself.
*/
SerializerProvider serializers = mapper.getSerializerProviderInstance();
// This module is reponsible for replacing non-deterministic objects
// with deterministic ones. Example convert Set to a sorted List.
SimpleModule module = new SimpleModule();
module.addSerializer(Collection.class,
new CustomDelegatingSerializerProvider(serializers, new CollectionToSortedListConverter(customComparators))
);
mapper.registerModule(module);
return mapper;
}
/*
* We need this class to delegate to the original SerializerProvider
* before we added our module to it. If we have a Collection -> Collection converter
* it delegates to itself and infinite loops until the stack overflows.
*/
private static class CustomDelegatingSerializerProvider extends StdDelegatingSerializer
{
private final SerializerProvider serializerProvider;
private CustomDelegatingSerializerProvider(SerializerProvider serializerProvider,
Converter<?, ?> converter)
{
super(converter);
this.serializerProvider = serializerProvider;
}
@Override
protected StdDelegatingSerializer withDelegate(Converter<Object,?> converter,
JavaType delegateType, JsonSerializer<?> delegateSerializer)
{
return new StdDelegatingSerializer(converter, delegateType, delegateSerializer);
}
/*
*  If we do not override this method to delegate to the original
*  serializerProvider we get a stack overflow exception because it recursively
*  calls itself. Basically we are hijacking the Collection serializer to first
*  sort the list then delegate it back to the original serializer.
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)
throws JsonMappingException
{
return super.createContextual(serializerProvider, property);
}
}
private static class CollectionToSortedListConverter extends StdConverter<Collection<?>, Collection<?>>
{
private final CustomComparators customComparators;
public CollectionToSortedListConverter(CustomComparators customComparators) {
this.customComparators = customComparators;
}
@Override
public Collection<? extends Object> convert(Collection<?> value)
{
if (value == null || value.isEmpty())
{
return Collections.emptyList();
}
/**
* Sort all elements by class first, then by our custom comparator.
* If the collection is heterogeneous or has anonymous classes its useful
* to first sort by the class name then by the comparator. We don't care
* about that actual sort order, just that it is deterministic.
*/
Comparator<Object> comparator = Comparator.comparing(x -> x.getClass().getName())
.thenComparing(customComparators::compare);
Collection<? extends Object> filtered = Seq.seq(value)
.filter(Objects::nonNull)
.sorted(comparator)
.toList();
if (filtered.isEmpty())
{
return Collections.emptyList();
}
return filtered;
}
}
public static class CustomComparators {
private final LinkedHashMap<Class<?>, Comparator<? extends Object>> customComparators;
public CustomComparators() {
customComparators = new LinkedHashMap<>();
}
public <T> void addConverter(Class<T> clazz, Comparator<?> comparator) {
customComparators.put(clazz, comparator);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public int compare(Object first, Object second) {
// If the object is comparable use its comparator
if (first instanceof Comparable) {
return ((Comparable) first).compareTo(second);
}
// If the object is not comparable try a custom supplied comparator
for (Entry<Class<?>, Comparator<?>> entry : customComparators.entrySet()) {
Class<?> clazz = entry.getKey();
if (first.getClass().isAssignableFrom(clazz)) {
Comparator<Object> comparator = (Comparator<Object>) entry.getValue();
return comparator.compare(first, second);
}
}
// we have no way to order the collection so fail hard
String message = String.format("Cannot compare object of type %s without a custom comparator", first.getClass().getName());
throw new UnsupportedOperationException(message);
}
}
}

我做了一个实用程序类来规范化json。它按键和值对属性进行排序,对值进行排序将它们转换为 json 字符串。性能不是最好的,但它有效。

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
public final class JsonNormalized {
private static final Logger LOGGER = LoggerFactory.getLogger(JsonNormalized.class);
private JsonNormalized() {
}
public static String normalize(String json) {
return serialize(deserialize(json));
}
private static String serialize(Object object) {
try {
return getObjectMapper().writeValueAsString(object);
} catch (JsonProcessingException e) {
LOGGER.error("Error serializing json", e);
throw new RuntimeException(e);
}
}
private static Object deserialize(String json) {
try {
JsonObject jsonObject = getObjectMapper().readValue(json, JsonObject.class);
return jsonObject.getData();
} catch (IOException e) {
LOGGER.error("Error deserializing json", e);
throw new RuntimeException(e);
}
}
private static ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
mapper.configure(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.disable(SerializationFeature.INDENT_OUTPUT);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
SimpleModule module = new SimpleModule();
module.addDeserializer(JsonObject.class, new JSONCustomDeserializer());
mapper.registerModule(module);
return mapper;
}
}
class JsonObject {
final Object data;
JsonObject(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
}
class JSONCustomDeserializer extends JsonDeserializer<JsonObject> {
@Override
public JsonObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
return new JsonObject(toObject(node));
}
public Object toObject(JsonNode node) {
if (node.fields().hasNext()) {
Map<String, Object> mapResult = new TreeMap<>();
for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entryChildren = it.next();
String childrenKey = entryChildren.getKey();
JsonNode children = entryChildren.getValue();
mapResult.put(childrenKey, children);
}
return new JsonObject(mapResult);
} else if (node.elements().hasNext()) {
List<Object> listResult = new ArrayList<Object>();
for (Iterator<JsonNode> it = node.elements(); it.hasNext(); ) {
JsonNode children = it.next();
listResult.add(children);
}
Collections.sort(listResult, (lhs, rhs) -> {
String lJson = JsonNormalized.serialize(lhs);
String rJson = JsonNormalized.serialize(rhs);
return lJson.compareTo(rJson);
});
return listResult;
} else {
return node.asText();
}
}
}

最新更新