2017-08-03 37 views
1

我希望能夠跨JVM生成任何Java POJO的MD5校驗和。方法是將對象序列化爲JSON,然後將MD5和JSON序列化。如何創建一個確定性的Jackson ObjectMapper?

問題是傑森的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和地圖上字段排序的兩個問題。

下一個挑戰是在飛行中修改任何集合並對其進行排序。這要求每個集合中的每個元素都是可排序的,但讓我們假設現在可以。

有沒有一種方法來攔截每個集合並在序列化之前進行分類?

回答

0

我用下面的代碼實現了這一點。閱讀更多關於Creating a somewhat deterministic 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); 
     } 
    } 
} 
相關問題