You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@felix.apache.org by David Leangen <os...@leangen.net> on 2016/08/24 16:21:19 UTC

[Converter] Map -> Object with generics

Hi!

I’m having a bit of trouble, so am fishing for ideas. Maybe there is a simple answer.

If I convert from Map—>DTO, and the DTO has in its tree a generic field, how can I tell the converter the correct type so that it does not get converted as a Map?

Example:

BottomDTO {
  public String a;
  public String b;

MiddleDTO<T> {
  public T bottom;
}

TopDTO<T> {
  public MiddleDTO<T> middle;
}

If I just do this, then Map is used for the value of bottom, which will cause a ClassCastException sometime later during execution:

  // Convert from Map to TopDTO<BottomDTO>
  converter.convert(someMap).to(TopDTO.class);


Thanks!
=David



Re: [Converter] Map -> Object with generics

Posted by David Leangen <os...@leangen.net>.
Hi!

After some experimentation, I think I may have found a way to make this work. It would require one update to the code, though, as I describe further below.

The trick for me was to create a subclass of TypeReference at runtime using known information like so:

        TypeReference<AbstractDTO<E>> tr = new TypeReference<AbstractDTO<E>>() {
            @Override
            public Type getType() {

                ParameterizedType t = (ParameterizedType)super.getType();

                return new ParameterizedType() {

                    @Override
                    public String getTypeName() {
                        return type.getTypeName();
                    }

                    @Override
                    public Type getRawType() {
                        return type; // <— this is the type of the runtime "envelope" object, i.e. object.getClass()
                    }
                    
                    @Override
                    public Type getOwnerType() {

                        return t.getOwnerType();
                    }
                    
                    @Override
                    public Type[] getActualTypeArguments() {
                        return new Type[]{ entityType }; // <— This is the class of the contained generic Parameter
                    }
                };                
            }
        };

In the example above, the “AbstractDTO” is an abstract superclass of each TransactionDTO (an object required by Prevayler to wrap a transaction). The actual type of the entity to persist is passed as a Type argument from the application (i.e. the backend does not know the static type).

The above seems to work… but for one problem.

In the method below, the type information is not used when retrieving the DTO field.

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private <T> T convertToDTO(Class<T> targetCls) {
        Map m = mapView(object, converter);

        try {
            T dto = targetCls.newInstance();

            for (Map.Entry entry : (Set<Map.Entry>) m.entrySet()) {
                try {
                    Field f = targetCls.getField(entry.getKey().toString());
                    Object val = entry.getValue();
                    // ****** HERE!!! ****** Need to use the type parameter somehow
                    f.set(dto, converter.convert(val).to(f.getType()));
                } catch (NoSuchFieldException e) {
                }
            }

            return dto;
        } catch (Exception e) {
            throw new ConversionException("Cannot create DTO " + targetCls, e);
        }
    }


The type for the field is f.getType(), which ignores the generic parameter type provided with the TypeReference. We would somehow need to return the correct type for this field, and the field conversion should work as expected and return the correct type instead of returning a Map.

With a fix for the above, I am pretty sure this would work. Any idea how to make that happen?

Anyway, please let me know what you think.


Cheers,
=David



> On Aug 25, 2016, at 5:19 AM, David Bosschaert <da...@gmail.com> wrote:
> 
> For DTOs, I think the following should be ok:
> 
> class MyDTO {
>  List<Long> foo;
> }
> 
> In any case, I think the converter should try to honour generics where
> possible, so if you have a JavaBean with generics it should also work
> class FooBean<T> {
>  T getFoo();
> }
> 
> In this case you should be able to do:
> FooBean<Bar> foo = converter.convert(something).to(new
> TypeReference<FooBean<Bar>>(){});
> 
> I don't think it's implemented for JavaBeans yet, but I'll try to add this
> soon.
> 
> Cheers,
> 
> David
> 
> On 24 August 2016 at 20:06, Raymond Auge <ra...@liferay.com> wrote:
> 
>> I'm not really sure that defining DTOs with generics is even acceptable!
>> 
>> I think the primary point of a DTO is to be completely concrete.
>> 
>> - Ray
>> 
>> On Wed, Aug 24, 2016 at 1:39 PM, David Bosschaert <
>> david.bosschaert@gmail.com> wrote:
>> 
>>> BTW I'm just noticing that the generic type arguments are not yet used
>> for
>>> conversions to DTOs, if you have a test case or even a patch that would
>> be
>>> great :)
>>> 
>>> Cheers,
>>> 
>>> David
>>> 
>>> On 24 August 2016 at 18:38, David Bosschaert <david.bosschaert@gmail.com
>>> 
>>> wrote:
>>> 
>>>> Hi David,
>>>> 
>>>> The current converter implementation actually has (some) support for
>> this
>>>> already via the TypeReference-based APIs. Basically this generic
>>>> information is preserved if you create a subclass for your type, and
>> the
>>>> way to do this is by creating an (anonymous) TypeReference subclass.
>>>> 
>>>> You can find an example in the ConverterMapTest.
>>> testGenericMapConversion()
>>>> [1]
>>>> 
>>>> Basically what that test does is convert this map:
>>>>  Map<Integer, String> m1 = Collections.singletonMap(42, "987654321");
>>>> into a Map<String, Long>. So the number 42 needs to be converted into a
>>>> String and the string "987654321" needs to be converted into a long.
>>>> 
>>>> An anonymous TypeReference subclass is used to tell the converter to do
>>>> this:
>>>>  Map<String, Long> m2 = converter.convert(m1).to(new
>>>> TypeReference<Map<String, Long>>(){});
>>>> 
>>>> Then at then end of the test you'll see that the correct converted
>> types
>>>> are being converted to are asserted.
>>>> 
>>>> So in your case you should be able to specify the conversion as:
>>>>  TopDTO<BottomDTO> dto = converter.convert(someMap).to(new
>>>> TypeReference<TopDTO<BottomDTO>>(){});
>>>> 
>>>> Hope this works for you :) The implementation of TypeReference-based
>> APIs
>>>> isn't completely finished, so if you find an issue, let us know!
>>>> 
>>>> Cheers,
>>>> 
>>>> David
>>>> 
>>>> [1] https://svn.apache.org/viewvc/felix/trunk/converter/
>>>> src/test/java/org/apache/felix/converter/impl/
>>> ConverterMapTest.java?view=
>>>> markup#l54
>>>> 
>>>> On 24 August 2016 at 18:21, David Leangen <os...@leangen.net> wrote:
>>>> 
>>>>> 
>>>>> Hi!
>>>>> 
>>>>> I’m having a bit of trouble, so am fishing for ideas. Maybe there is a
>>>>> simple answer.
>>>>> 
>>>>> If I convert from Map—>DTO, and the DTO has in its tree a generic
>> field,
>>>>> how can I tell the converter the correct type so that it does not get
>>>>> converted as a Map?
>>>>> 
>>>>> Example:
>>>>> 
>>>>> BottomDTO {
>>>>>  public String a;
>>>>>  public String b;
>>>>> 
>>>>> MiddleDTO<T> {
>>>>>  public T bottom;
>>>>> }
>>>>> 
>>>>> TopDTO<T> {
>>>>>  public MiddleDTO<T> middle;
>>>>> }
>>>>> 
>>>>> If I just do this, then Map is used for the value of bottom, which
>> will
>>>>> cause a ClassCastException sometime later during execution:
>>>>> 
>>>>>  // Convert from Map to TopDTO<BottomDTO>
>>>>>  converter.convert(someMap).to(TopDTO.class);
>>>>> 
>>>>> 
>>>>> Thanks!
>>>>> =David
>>>>> 
>>>>> 
>>>>> 
>>>> 
>>> 
>> 
>> 
>> 
>> --
>> *Raymond Augé* <http://www.liferay.com/web/raymond.auge/profile>
>> (@rotty3000)
>> Senior Software Architect *Liferay, Inc.* <http://www.liferay.com>
>> (@Liferay)
>> Board Member & EEG Co-Chair, OSGi Alliance <http://osgi.org>
>> (@OSGiAlliance)
>> 


Re: [Converter] Map -> Object with generics

Posted by David Bosschaert <da...@gmail.com>.
For DTOs, I think the following should be ok:

class MyDTO {
  List<Long> foo;
}

In any case, I think the converter should try to honour generics where
possible, so if you have a JavaBean with generics it should also work
class FooBean<T> {
  T getFoo();
}

In this case you should be able to do:
FooBean<Bar> foo = converter.convert(something).to(new
TypeReference<FooBean<Bar>>(){});

I don't think it's implemented for JavaBeans yet, but I'll try to add this
soon.

Cheers,

David

On 24 August 2016 at 20:06, Raymond Auge <ra...@liferay.com> wrote:

> I'm not really sure that defining DTOs with generics is even acceptable!
>
> I think the primary point of a DTO is to be completely concrete.
>
> - Ray
>
> On Wed, Aug 24, 2016 at 1:39 PM, David Bosschaert <
> david.bosschaert@gmail.com> wrote:
>
> > BTW I'm just noticing that the generic type arguments are not yet used
> for
> > conversions to DTOs, if you have a test case or even a patch that would
> be
> > great :)
> >
> > Cheers,
> >
> > David
> >
> > On 24 August 2016 at 18:38, David Bosschaert <david.bosschaert@gmail.com
> >
> > wrote:
> >
> > > Hi David,
> > >
> > > The current converter implementation actually has (some) support for
> this
> > > already via the TypeReference-based APIs. Basically this generic
> > > information is preserved if you create a subclass for your type, and
> the
> > > way to do this is by creating an (anonymous) TypeReference subclass.
> > >
> > > You can find an example in the ConverterMapTest.
> > testGenericMapConversion()
> > > [1]
> > >
> > > Basically what that test does is convert this map:
> > >   Map<Integer, String> m1 = Collections.singletonMap(42, "987654321");
> > > into a Map<String, Long>. So the number 42 needs to be converted into a
> > > String and the string "987654321" needs to be converted into a long.
> > >
> > > An anonymous TypeReference subclass is used to tell the converter to do
> > > this:
> > >   Map<String, Long> m2 = converter.convert(m1).to(new
> > > TypeReference<Map<String, Long>>(){});
> > >
> > > Then at then end of the test you'll see that the correct converted
> types
> > > are being converted to are asserted.
> > >
> > > So in your case you should be able to specify the conversion as:
> > >   TopDTO<BottomDTO> dto = converter.convert(someMap).to(new
> > > TypeReference<TopDTO<BottomDTO>>(){});
> > >
> > > Hope this works for you :) The implementation of TypeReference-based
> APIs
> > > isn't completely finished, so if you find an issue, let us know!
> > >
> > > Cheers,
> > >
> > > David
> > >
> > > [1] https://svn.apache.org/viewvc/felix/trunk/converter/
> > > src/test/java/org/apache/felix/converter/impl/
> > ConverterMapTest.java?view=
> > > markup#l54
> > >
> > > On 24 August 2016 at 18:21, David Leangen <os...@leangen.net> wrote:
> > >
> > >>
> > >> Hi!
> > >>
> > >> I’m having a bit of trouble, so am fishing for ideas. Maybe there is a
> > >> simple answer.
> > >>
> > >> If I convert from Map—>DTO, and the DTO has in its tree a generic
> field,
> > >> how can I tell the converter the correct type so that it does not get
> > >> converted as a Map?
> > >>
> > >> Example:
> > >>
> > >> BottomDTO {
> > >>   public String a;
> > >>   public String b;
> > >>
> > >> MiddleDTO<T> {
> > >>   public T bottom;
> > >> }
> > >>
> > >> TopDTO<T> {
> > >>   public MiddleDTO<T> middle;
> > >> }
> > >>
> > >> If I just do this, then Map is used for the value of bottom, which
> will
> > >> cause a ClassCastException sometime later during execution:
> > >>
> > >>   // Convert from Map to TopDTO<BottomDTO>
> > >>   converter.convert(someMap).to(TopDTO.class);
> > >>
> > >>
> > >> Thanks!
> > >> =David
> > >>
> > >>
> > >>
> > >
> >
>
>
>
> --
> *Raymond Augé* <http://www.liferay.com/web/raymond.auge/profile>
>  (@rotty3000)
> Senior Software Architect *Liferay, Inc.* <http://www.liferay.com>
>  (@Liferay)
> Board Member & EEG Co-Chair, OSGi Alliance <http://osgi.org>
> (@OSGiAlliance)
>

Re: [Converter] Map -> Object with generics

Posted by Raymond Auge <ra...@liferay.com>.
I'm not really sure that defining DTOs with generics is even acceptable!

I think the primary point of a DTO is to be completely concrete.

- Ray

On Wed, Aug 24, 2016 at 1:39 PM, David Bosschaert <
david.bosschaert@gmail.com> wrote:

> BTW I'm just noticing that the generic type arguments are not yet used for
> conversions to DTOs, if you have a test case or even a patch that would be
> great :)
>
> Cheers,
>
> David
>
> On 24 August 2016 at 18:38, David Bosschaert <da...@gmail.com>
> wrote:
>
> > Hi David,
> >
> > The current converter implementation actually has (some) support for this
> > already via the TypeReference-based APIs. Basically this generic
> > information is preserved if you create a subclass for your type, and the
> > way to do this is by creating an (anonymous) TypeReference subclass.
> >
> > You can find an example in the ConverterMapTest.
> testGenericMapConversion()
> > [1]
> >
> > Basically what that test does is convert this map:
> >   Map<Integer, String> m1 = Collections.singletonMap(42, "987654321");
> > into a Map<String, Long>. So the number 42 needs to be converted into a
> > String and the string "987654321" needs to be converted into a long.
> >
> > An anonymous TypeReference subclass is used to tell the converter to do
> > this:
> >   Map<String, Long> m2 = converter.convert(m1).to(new
> > TypeReference<Map<String, Long>>(){});
> >
> > Then at then end of the test you'll see that the correct converted types
> > are being converted to are asserted.
> >
> > So in your case you should be able to specify the conversion as:
> >   TopDTO<BottomDTO> dto = converter.convert(someMap).to(new
> > TypeReference<TopDTO<BottomDTO>>(){});
> >
> > Hope this works for you :) The implementation of TypeReference-based APIs
> > isn't completely finished, so if you find an issue, let us know!
> >
> > Cheers,
> >
> > David
> >
> > [1] https://svn.apache.org/viewvc/felix/trunk/converter/
> > src/test/java/org/apache/felix/converter/impl/
> ConverterMapTest.java?view=
> > markup#l54
> >
> > On 24 August 2016 at 18:21, David Leangen <os...@leangen.net> wrote:
> >
> >>
> >> Hi!
> >>
> >> I’m having a bit of trouble, so am fishing for ideas. Maybe there is a
> >> simple answer.
> >>
> >> If I convert from Map—>DTO, and the DTO has in its tree a generic field,
> >> how can I tell the converter the correct type so that it does not get
> >> converted as a Map?
> >>
> >> Example:
> >>
> >> BottomDTO {
> >>   public String a;
> >>   public String b;
> >>
> >> MiddleDTO<T> {
> >>   public T bottom;
> >> }
> >>
> >> TopDTO<T> {
> >>   public MiddleDTO<T> middle;
> >> }
> >>
> >> If I just do this, then Map is used for the value of bottom, which will
> >> cause a ClassCastException sometime later during execution:
> >>
> >>   // Convert from Map to TopDTO<BottomDTO>
> >>   converter.convert(someMap).to(TopDTO.class);
> >>
> >>
> >> Thanks!
> >> =David
> >>
> >>
> >>
> >
>



-- 
*Raymond Augé* <http://www.liferay.com/web/raymond.auge/profile>
 (@rotty3000)
Senior Software Architect *Liferay, Inc.* <http://www.liferay.com>
 (@Liferay)
Board Member & EEG Co-Chair, OSGi Alliance <http://osgi.org> (@OSGiAlliance)

Re: [Converter] Map -> Object with generics

Posted by David Bosschaert <da...@gmail.com>.
BTW I'm just noticing that the generic type arguments are not yet used for
conversions to DTOs, if you have a test case or even a patch that would be
great :)

Cheers,

David

On 24 August 2016 at 18:38, David Bosschaert <da...@gmail.com>
wrote:

> Hi David,
>
> The current converter implementation actually has (some) support for this
> already via the TypeReference-based APIs. Basically this generic
> information is preserved if you create a subclass for your type, and the
> way to do this is by creating an (anonymous) TypeReference subclass.
>
> You can find an example in the ConverterMapTest.testGenericMapConversion()
> [1]
>
> Basically what that test does is convert this map:
>   Map<Integer, String> m1 = Collections.singletonMap(42, "987654321");
> into a Map<String, Long>. So the number 42 needs to be converted into a
> String and the string "987654321" needs to be converted into a long.
>
> An anonymous TypeReference subclass is used to tell the converter to do
> this:
>   Map<String, Long> m2 = converter.convert(m1).to(new
> TypeReference<Map<String, Long>>(){});
>
> Then at then end of the test you'll see that the correct converted types
> are being converted to are asserted.
>
> So in your case you should be able to specify the conversion as:
>   TopDTO<BottomDTO> dto = converter.convert(someMap).to(new
> TypeReference<TopDTO<BottomDTO>>(){});
>
> Hope this works for you :) The implementation of TypeReference-based APIs
> isn't completely finished, so if you find an issue, let us know!
>
> Cheers,
>
> David
>
> [1] https://svn.apache.org/viewvc/felix/trunk/converter/
> src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java?view=
> markup#l54
>
> On 24 August 2016 at 18:21, David Leangen <os...@leangen.net> wrote:
>
>>
>> Hi!
>>
>> I’m having a bit of trouble, so am fishing for ideas. Maybe there is a
>> simple answer.
>>
>> If I convert from Map—>DTO, and the DTO has in its tree a generic field,
>> how can I tell the converter the correct type so that it does not get
>> converted as a Map?
>>
>> Example:
>>
>> BottomDTO {
>>   public String a;
>>   public String b;
>>
>> MiddleDTO<T> {
>>   public T bottom;
>> }
>>
>> TopDTO<T> {
>>   public MiddleDTO<T> middle;
>> }
>>
>> If I just do this, then Map is used for the value of bottom, which will
>> cause a ClassCastException sometime later during execution:
>>
>>   // Convert from Map to TopDTO<BottomDTO>
>>   converter.convert(someMap).to(TopDTO.class);
>>
>>
>> Thanks!
>> =David
>>
>>
>>
>

Re: [Converter] Map -> Object with generics

Posted by David Bosschaert <da...@gmail.com>.
Hi David,

The current converter implementation actually has (some) support for this
already via the TypeReference-based APIs. Basically this generic
information is preserved if you create a subclass for your type, and the
way to do this is by creating an (anonymous) TypeReference subclass.

You can find an example in the ConverterMapTest.testGenericMapConversion()
[1]

Basically what that test does is convert this map:
  Map<Integer, String> m1 = Collections.singletonMap(42, "987654321");
into a Map<String, Long>. So the number 42 needs to be converted into a
String and the string "987654321" needs to be converted into a long.

An anonymous TypeReference subclass is used to tell the converter to do
this:
  Map<String, Long> m2 = converter.convert(m1).to(new
TypeReference<Map<String, Long>>(){});

Then at then end of the test you'll see that the correct converted types
are being converted to are asserted.

So in your case you should be able to specify the conversion as:
  TopDTO<BottomDTO> dto = converter.convert(someMap).to(new
TypeReference<TopDTO<BottomDTO>>(){});

Hope this works for you :) The implementation of TypeReference-based APIs
isn't completely finished, so if you find an issue, let us know!

Cheers,

David

[1]
https://svn.apache.org/viewvc/felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/ConverterMapTest.java?view=markup#l54

On 24 August 2016 at 18:21, David Leangen <os...@leangen.net> wrote:

>
> Hi!
>
> I’m having a bit of trouble, so am fishing for ideas. Maybe there is a
> simple answer.
>
> If I convert from Map—>DTO, and the DTO has in its tree a generic field,
> how can I tell the converter the correct type so that it does not get
> converted as a Map?
>
> Example:
>
> BottomDTO {
>   public String a;
>   public String b;
>
> MiddleDTO<T> {
>   public T bottom;
> }
>
> TopDTO<T> {
>   public MiddleDTO<T> middle;
> }
>
> If I just do this, then Map is used for the value of bottom, which will
> cause a ClassCastException sometime later during execution:
>
>   // Convert from Map to TopDTO<BottomDTO>
>   converter.convert(someMap).to(TopDTO.class);
>
>
> Thanks!
> =David
>
>
>