Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-9257

Resolve Collection element types during conversion

    Details

    • Last commented by a User:
      true

      Description

      Between 3.0 and 3.1 the behavior of TypeDescriptor.forObject changed.
      If the value is a map or a collection then elementTypeDescriptor, mapKeyTypeDescriptor, and mapValueTypeDescriptor remain uninitialized.
      This in turn precludes conversions from happening.

      Our application has a method that accepts a Map where the key is an enum type.
      In 3.0.x we received a map where keys were enums, in 3.1 the keys are strings.

      I'm attaching an updated revision of TypeDescriptor, only the method TypeDescriptor.forObject was changed.
      It used to be:

       
      	public static TypeDescriptor forObject(Object source) {
      		return (source != null ? valueOf(source.getClass()) : null);
      	}

      I changed it to:

          public static TypeDescriptor forObject(Object source) {
              if (source == null) {
                  return null;
              }
              else if (source instanceof Collection<?>) {
                  Class<?> elementType = CollectionUtils.findCommonElementType((Collection<?>)source);
                  return new TypeDescriptor(source.getClass(), elementType==null ? null : new TypeDescriptor(elementType));
              }
              else if (source instanceof Map<?, ?>) {
                  Class<?> keyType = CollectionUtils.findCommonElementType(((Map<?, ?>) source).keySet());
                  Class<?> valueType = CollectionUtils.findCommonElementType(((Map<?, ?>) source).values());
                  return new TypeDescriptor(source.getClass(),
                          keyType==null ? null : new TypeDescriptor(keyType), 
                          valueType==null ? null : new TypeDescriptor(valueType));
              }
              else {
                  return valueOf(source.getClass());
              }
          }

        Issue Links

          Activity

          Hide
          finn Finn Edward Merkel added a comment -

          I can confirm this. In our unit tests, TypeDescriptor.forObject(someEnumSet) no longer creates a TypeDescriptor with a non-null elementType (which is now an elementTypeDescriptor). Fortunately, creating a TypeDescriptor from a field still works.

          Show
          finn Finn Edward Merkel added a comment - I can confirm this. In our unit tests, TypeDescriptor.forObject(someEnumSet) no longer creates a TypeDescriptor with a non-null elementType (which is now an elementTypeDescriptor). Fortunately, creating a TypeDescriptor from a field still works.
          Hide
          alex322 Sandu Turcan added a comment - - edited

          Here is an updated version, it handles cases when the object type is already fully parameterized.
          I had to make this change after I patched GenericCollectionTypeResolver as described in SPR-9276.
          Without it we can get either Properties<String,String> or Properties<Object,Object> depending on the context, which in turn triggers unnecessary conversions.
          This makes it consistent.

          public static TypeDescriptor forObject(Object source) {
              if (source == null) {
                  return null;
              }
              else if (source instanceof Collection<?>) {
                  @SuppressWarnings("unchecked")
                  Class<? extends Collection<?>> sourceClass = (Class<? extends Collection<?>>) source.getClass();
                  Class<?> elementType = GenericCollectionTypeResolver.getCollectionType(sourceClass);
                  if (elementType==null) elementType = CollectionUtils.findCommonElementType((Collection<?>)source);
                  return new TypeDescriptor(sourceClass, elementType==null ? null : new TypeDescriptor(elementType));
              }
              else if (source instanceof Map<?, ?>) {
                  @SuppressWarnings("unchecked")
                  Class<? extends Map<?,?>> sourceClass = (Class<? extends Map<?,?>>) source.getClass();
                  Class<?> keyType = GenericCollectionTypeResolver.getMapKeyType(sourceClass),
                          valueType = GenericCollectionTypeResolver.getMapValueType(sourceClass);
                  if (keyType==null) keyType = CollectionUtils.findCommonElementType(((Map<?, ?>) source).keySet());
                  if (valueType==null) valueType = CollectionUtils.findCommonElementType(((Map<?, ?>) source).values());
                  return new TypeDescriptor(sourceClass,
                          keyType==null ? null : new TypeDescriptor(keyType),
                          valueType==null ? null : new TypeDescriptor(valueType));
              }
              else {
                  return valueOf(source.getClass());
              }
          }

          Show
          alex322 Sandu Turcan added a comment - - edited Here is an updated version, it handles cases when the object type is already fully parameterized. I had to make this change after I patched GenericCollectionTypeResolver as described in SPR-9276 . Without it we can get either Properties<String,String> or Properties<Object,Object> depending on the context, which in turn triggers unnecessary conversions. This makes it consistent. public static TypeDescriptor forObject(Object source) { if (source == null ) { return null ; } else if (source instanceof Collection<?>) { @SuppressWarnings ( "unchecked" ) Class<? extends Collection<?>> sourceClass = (Class<? extends Collection<?>>) source.getClass(); Class<?> elementType = GenericCollectionTypeResolver.getCollectionType(sourceClass); if (elementType== null ) elementType = CollectionUtils.findCommonElementType((Collection<?>)source); return new TypeDescriptor(sourceClass, elementType== null ? null : new TypeDescriptor(elementType)); } else if (source instanceof Map<?, ?>) { @SuppressWarnings ( "unchecked" ) Class<? extends Map<?,?>> sourceClass = (Class<? extends Map<?,?>>) source.getClass(); Class<?> keyType = GenericCollectionTypeResolver.getMapKeyType(sourceClass), valueType = GenericCollectionTypeResolver.getMapValueType(sourceClass); if (keyType== null ) keyType = CollectionUtils.findCommonElementType(((Map<?, ?>) source).keySet()); if (valueType== null ) valueType = CollectionUtils.findCommonElementType(((Map<?, ?>) source).values()); return new TypeDescriptor(sourceClass, keyType== null ? null : new TypeDescriptor(keyType), valueType== null ? null : new TypeDescriptor(valueType)); } else { return valueOf(source.getClass()); } }
          Hide
          pwebb Phil Webb added a comment -

          Hi Sandu,

          Looking into this issue I am not sure that using CollectionUtils.findCommonElementType is something that we would want to put back into TypeDescriptor. The problem with findCommonElementType is that it requires elements in the collection in order to work.

          I do think that GenericCollectionTypeResolver should be used for and I have submitted a pull request to address this issue [1].

          Cheers,
          Phil.

          [1] https://github.com/SpringSource/spring-framework/pull/156

          Show
          pwebb Phil Webb added a comment - Hi Sandu, Looking into this issue I am not sure that using CollectionUtils.findCommonElementType is something that we would want to put back into TypeDescriptor. The problem with findCommonElementType is that it requires elements in the collection in order to work. I do think that GenericCollectionTypeResolver should be used for and I have submitted a pull request to address this issue [1] . Cheers, Phil. [1] https://github.com/SpringSource/spring-framework/pull/156
          Hide
          cbeams Chris Beams added a comment -

          commit 5a1f924ac328827c31ced745a65867d4a1feca17
          Author: Phillip Webb <[email protected]>
          Commit: Chris Beams <[email protected]>
           
              Resolve Collection element types during conversion
              
              TypeDescriptor.valueOf now uses GenericCollectionTypeResolver to resolve
              Collection and Map element types whereas previously this information was
              simply not supported, i.e. null.
              
              Issue: SPR-9257

          Show
          cbeams Chris Beams added a comment - commit 5a1f924ac328827c31ced745a65867d4a1feca17 Author: Phillip Webb <[email protected]> Commit: Chris Beams <[email protected]>   Resolve Collection element types during conversion TypeDescriptor.valueOf now uses GenericCollectionTypeResolver to resolve Collection and Map element types whereas previously this information was simply not supported, i.e. null. Issue: SPR-9257

            People

            • Assignee:
              pwebb Phil Webb
              Reporter:
              alex322 Sandu Turcan
              Last updater:
              Chris Beams
            • Votes:
              1 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                5 years, 4 weeks, 3 days ago