Parsing/serializing 'Optional' values

classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

Parsing/serializing 'Optional' values

Remko Tronçon
Hi,

I'm having problems parsing/serializing classes to XML that have
values stored in a generic container, such as for example Optional<T>
( http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html
). When the container is empty (e.g. !Optional.isPresent), i don't
want the field to be serialized at all.

For example, a field
 class Bar {
    Optional<String> myField1;
    String myField2;
  }
would either serialize to:
   <bar>
      <myField1>Some string</myField1>
      <myField2>Some other string</myField2>
    <bar>
or to
   <bar>
        <myField2>Some other string</myField2>
   </bar>
depending on whether myField1 contains a value or not.

Concretely, I have 2 problems:
- If I write a convertor for the Optional type, I don't know what the
right way is to marshal the containing type (basically, I just want to
delegate the serializing further to the default serialization
behavior)
- A convertor doesn't control the generated tags, so I can't omit
fields from the convertor. I also can't write a mapper, since the
condition on whether to omit an element is based on the actual value
of the object, and as far as i can tell, the check is based on static
type information only. This problem is less crucial than the previous
one, since I suppose i can work around this by writing a filter to
strip empty elements from XML.

Any suggestions?

thanks!
Remko

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Parsing/serializing 'Optional' values

Jörg Schaible-2
Hi Remko,

Remko Tronçon wrote:

> Hi,
>
> I'm having problems parsing/serializing classes to XML that have
> values stored in a generic container, such as for example Optional<T>
> (
> http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html
> ). When the container is empty (e.g. !Optional.isPresent), i don't want
> the field to be serialized at all.
>
> For example, a field
>  class Bar {
>     Optional<String> myField1;
>     String myField2;
>   }
> would either serialize to:
>    <bar>
>       <myField1>Some string</myField1>
>       <myField2>Some other string</myField2>
>     <bar>
> or to
>    <bar>
>         <myField2>Some other string</myField2>
>    </bar>
> depending on whether myField1 contains a value or not.
>
> Concretely, I have 2 problems:
> - If I write a convertor for the Optional type, I don't know what the
> right way is to marshal the containing type (basically, I just want to
> delegate the serializing further to the default serialization
> behavior)

Look at the converter tutorial. The last example at the end will do this
also.

> - A convertor doesn't control the generated tags, so I can't omit
> fields from the convertor. I also can't write a mapper, since the
> condition on whether to omit an element is based on the actual value
> of the object, and as far as i can tell, the check is based on static
> type information only. This problem is less crucial than the previous
> one, since I suppose i can work around this by writing a filter to
> strip empty elements from XML.

Actually XStream works only on the structure of the objects, never on the
values itself. If you write your own converter, you're free to do so, but -
as you have already recognized - you would need one for every type that
contains an Optional. I am aware that this is not what you want.

> Any suggestions?

Since I assume, that your type containing the Optional is currently handled
by one of XStream's reflection-based converters, you may use following non-
obvious approach by wrapping the ReflectionProvider:

================ %< ==============
 ReflectionProvider reflectionProvider =
   new ObjectFilteringReflectionProvider(
     new JVM().bestReflectionProvider());
 XStream xstream = new XStream(reflectionProvider);

 ...

 class ObjectFilteringReflectionProvider extends ReflectionProviderWrapper
 {
   public ObjectFilteringReflectionProvider(ReflectionProvider wrapped)
   {
     super(wrapped);
   }

   public void visitSerializableFields(
     final Object object, final Visitor visitor)
   {
     wrapped.visitSerializableFields(object, new Visitor() {
       public void visit(
         String name, Class type, Class definedIn, Object value)
       {
         if (!(value instanceof Optional)
             || ((Optional)value).orNull() != null) {
           visitor.visit(name, type, definedIn, value);
         }
       }
     });
   }
 }
================ %< ==============

The ReflectionProvider's visitor is used for serialization by all
reflection-based converters to visit any field that must be handled by the
converter. The wrapper above will now silently drop any Optional-derived
type that contains null.

I also assume that this is Java to XML only i.e. no deserialization, because
those fields containing Optionals referencing null will no longer be
initialized.

Hope this helps,
Jörg


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Parsing/serializing 'Optional' values

Remko Tronçon
Hi Jörg

> Look at the converter tutorial. The last example at the end will do this
> also.

Oh, I must have missed that one. That seems to indeed do exactly what I wanted.

> Since I assume, that your type containing the Optional is currently handled
> by one of XStream's reflection-based converters, you may use following non-
> obvious approach by wrapping the ReflectionProvider:

Works like a charm!

> I also assume that this is Java to XML only i.e. no deserialization, because
> those fields containing Optionals referencing null will no longer be
> initialized.

Understood. I'm falling back on readResolve for filling in the gaps.

Thanks a lot for your help!
Remko

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Parsing/serializing 'Optional' values

Jörg Schaible-3
Hi Remko,

Remko Tronçon wrote:

> Hi Jörg
>
>> Look at the converter tutorial. The last example at the end will do this
>> also.
>
> Oh, I must have missed that one. That seems to indeed do exactly what I
> wanted.
>
>> Since I assume, that your type containing the Optional is currently
>> handled by one of XStream's reflection-based converters, you may use
>> following non- obvious approach by wrapping the ReflectionProvider:
>
> Works like a charm!
>
>> I also assume that this is Java to XML only i.e. no deserialization,
>> because those fields containing Optionals referencing null will no longer
>> be initialized.
>
> Understood. I'm falling back on readResolve for filling in the gaps.

Yeah, but take care, readResolve is not hierarchically called in contrast to
readObject.

> Thanks a lot for your help!

You're welcome.

- Jörg


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Parsing/serializing 'Optional' values

Remko Tronçon
In reply to this post by Jörg Schaible-2
Hi Jörg,

On 11 September 2013 01:19, Jörg Schaible <[hidden email]> wrote:
> Look at the converter tutorial. The last example at the end will do this
> also.

It seems I'm still missing a piece of the puzzle. When unmarshalling
an optional, I need to know the type to pass to convertAnother() for
unmarshalling the contained value. All
Unmarhsalingcontext.getRequiredtype() gives me is 'Optional', not the
actual type parameter. I tried doing this through reflection, but all
HierarchicalStreamReader can give me is a hint what the field name is,
not which class i need to look in. I also tried
'UnmarshalingContext.currentObject()', but this is null.

Am I missing something?

thanks!
Remko

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Parsing/serializing 'Optional' values

Jörg Schaible-2
Hi Remko,

Remko Tronçon wrote:

> Hi Jörg,
>
> On 11 September 2013 01:19, Jörg Schaible
> <[hidden email]> wrote:
>> Look at the converter tutorial. The last example at the end will do this
>> also.
>
> It seems I'm still missing a piece of the puzzle. When unmarshalling
> an optional, I need to know the type to pass to convertAnother() for
> unmarshalling the contained value. All
> Unmarhsalingcontext.getRequiredtype() gives me is 'Optional', not the
> actual type parameter. I tried doing this through reflection, but all
> HierarchicalStreamReader can give me is a hint what the field name is,
> not which class i need to look in. I also tried
> 'UnmarshalingContext.currentObject()', but this is null.
>
> Am I missing something?

UnmarshalingContext.getRequiredType contains normally the correct thing.
Question is, why does it not do it for you? What else have you configured
(using XStream methods or annotations) for the Optional types?

- Jörg



---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Parsing/serializing 'Optional' values

Remko Tronçon
Hi Jörg,

On 11 September 2013 21:48, Jörg Schaible <[hidden email]> wrote:
> UnmarshalingContext.getRequiredType contains normally the correct thing.

Well, it is correct, in the sense that it says it requires an
"Optional". However, where would i get the actual generic type? (e.g.,
for Optional<String>, I want to get 'String.class'). I don't think you
can get this from the context.getRequiredType() Class return value,
can you?

thanks,
Remko

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Re: Parsing/serializing 'Optional' values

Jörg Schaible-3
Hi Remko,

Remko Tronçon wrote:

> Hi Jörg,
>
> On 11 September 2013 21:48, Jörg Schaible
> <[hidden email]> wrote:
>> UnmarshalingContext.getRequiredType contains normally the correct thing.
>
> Well, it is correct, in the sense that it says it requires an
> "Optional".

Well, since Optional is abstract, it should contain the real type. I thought
you talked about that.

> However, where would i get the actual generic type? (e.g.,
> for Optional<String>, I want to get 'String.class'). I don't think you
> can get this from the context.getRequiredType() Class return value,
> can you?

You might, but it depends on the concrete situation.

In any case, your converter has to know the proper type at deserialization
time, it is your converter's responsibility. XStream's converters will write
in such a case the class name into the attribute of the child element or use
the class name directly as tag name of the child element and use that to
create the proper type at deserialization. Assuming your converter receives
the Mapper in its constructor, your marshalling code would normally look
similar to:

============ %< ================
 Object object = ((Optional<?>)value).get();
 String name = mapper.serializableName(object.getClass())
 writer.startNode(name);
 context.convertAnother(object);
 writer.endNode();
============ %< ================

However, I simply guess that you try not to nest the value of the Optional
in your converter.

Consider this type from your example:

============ %< ================
 class Bar {
   Optional<String> myField1;
 }
============ %< ================

Since Optional is abstract, "myfield" must have been populated with a
concrete class, e.g. an instance of:

============ %< ================
 class OptionalString extends Optional<String> {
  ...
 }
============ %< ================

This will normally result in:

============ %< ================
 <Bar>
   <myField1 class="OptionalString">Hello, word!</myField1>
 </Bar>
============ %< ================

The class attribute is written (by the ReflectionCOnverter that handles
"Bar" types), because the instance in "myField1" has a different type
compared to the field's declaration. At deserialization time  
context.getRequiredType() should contain "OptionalString". And I am quite
sure that you can find the generic type with:

============ %< ================
 Class optional=context.getRequiredType();
 while(!Optional.class.equals(optional)) {
   optional = optional.getSuperclass();
 }
 Class genericType = optional.getGenericType();
============ %< ================

- Jörg



---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Re: Parsing/serializing 'Optional' values

Remko Tronçon
Hi Jörg,

> Since Optional is abstract, "myfield" must have been populated with a

Ah, I see that 'Optional' wasn't the best example. My version of
Optional isn't abstract, I directly
instantiate 'Optional<String>'.

You're right, I don't nest my output, so in my case,
  class Bar {
   Optional<String> myField1;
 }

becomes

  <Bar>
     <myField1>Hello, world!</myField1>
   </Bar>

> context.getRequiredType() should contain "OptionalString". And I am quite
> sure that you can find the generic type with:
>  Class optional=context.getRequiredType();
>  while(!Optional.class.equals(optional)) {
>    optional = optional.getSuperclass();
>  }
>  Class genericType = optional.getGenericType();

I would have hoped, but I don't think there is a 'getGenericType' on a
Class object, so I don't think I can get it from getRequiredType.
If I would indeed inherit from my Optional, I probably could have used
getGenericSuperclass() on the class.
I guess it would have also worked if getRequiredType() returned a Type
instead of a class?

thanks,
Remko

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Re: Re: Parsing/serializing 'Optional' values

Jörg Schaible-3
Remko Tronçon wrote:

> Hi Jörg,
>
>> Since Optional is abstract, "myfield" must have been populated with a
>
> Ah, I see that 'Optional' wasn't the best example. My version of
> Optional isn't abstract, I directly
> instantiate 'Optional<String>'.

Since Optional is an abstract class ... how do you instantiate it
"directly"?

>
> You're right, I don't nest my output, so in my case,
>   class Bar {
>    Optional<String> myField1;
>  }
>
> becomes
>
>   <Bar>
>      <myField1>Hello, world!</myField1>
>    </Bar>
>
>> context.getRequiredType() should contain "OptionalString". And I am quite
>> sure that you can find the generic type with:
>>  Class optional=context.getRequiredType();
>>  while(!Optional.class.equals(optional)) {
>>    optional = optional.getSuperclass();
>>  }
>>  Class genericType = optional.getGenericType();
>
> I would have hoped, but I don't think there is a 'getGenericType' on a
> Class object, so I don't think I can get it from getRequiredType.
> If I would indeed inherit from my Optional, I probably could have used
> getGenericSuperclass() on the class.
> I guess it would have also worked if getRequiredType() returned a Type
> instead of a class?

The question is simply, if the value from "getRequiredType()" is acquired by
the parent converter with "Bar.class.getDeclaredField("myField1").getType()"
in the end. Then you should be able to deduce the generic type.

The problem is that you cannot rely on it:

=============== %< ==============
 <list>
   <optional>Hello, world!</optional>
 </list>
=============== %< ==============

or

=============== %< ==============
 class Derived<T> extends Optional<T> {}

 <Bar>
   <myField1 class="Derived">Hello, world!</myField1>
 </Bar>
=============== %< ==============

No info about the generic type available in both cases. Additionally you
will have problems in this case:

=============== %< ==============
 class Bar {
   Optional<? extends CharSequence> myField1;
 }

 <Bar>
   <myField1>Hello, world!</myField1>
 </Bar>
=============== %< ==============

The generic type will not necessarily tell you about the real type of the
instance you have to create at deserialization time.

IMHO you have only two reliably possibilities. Either write the type of the
value into an own attribute or use multiple instances of your converter, let
them take the type as constructor argument and register those converters as
local ones individually.

Cheers,
Jörg


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Re: Re: Parsing/serializing 'Optional' values

Remko Tronçon
Hi Jörg,

> Since Optional is an abstract class ... how do you instantiate it
> "directly"?

That was the confusing part: I don't actually use the "Optional" from
Guava myself, I have my own Optional class which isn't abstract. It's
just a container of a reference to the given type.

> The question is simply, if the value from "getRequiredType()" is acquired by
> the parent converter with "Bar.class.getDeclaredField("myField1").getType()"
> in the end. Then you should be able to deduce the generic type.

My question is then: shouldn't the parent converter use
getGenericType() instead of getType()?
getType() returns a class, so for a declared field Optional<String>,
it will only give me "Optional"; from getGenericType(), I would have
been able to get the "String" part as well.

> IMHO you have only two reliably possibilities. Either write the type of the
> value into an own attribute or use multiple instances of your converter, let
> them take the type as constructor argument and register those converters as
> local ones individually.

Thanks for the suggestion. The multiple instances alternative sounds
doable for me.

cheers,
Remko

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Re: Re: Re: Parsing/serializing 'Optional' values

Jörg Schaible-2
Remko Tronçon wrote:

> Hi Jörg,
>
>> Since Optional is an abstract class ... how do you instantiate it
>> "directly"?
>
> That was the confusing part: I don't actually use the "Optional" from
> Guava myself, I have my own Optional class which isn't abstract. It's
> just a container of a reference to the given type.

OK.
 

>> The question is simply, if the value from "getRequiredType()" is acquired
>> by the parent converter with
>> "Bar.class.getDeclaredField("myField1").getType()" in the end. Then you
>> should be able to deduce the generic type.
>
> My question is then: shouldn't the parent converter use
> getGenericType() instead of getType()?
> getType() returns a class, so for a declared field Optional<String>,
> it will only give me "Optional"; from getGenericType(), I would have
> been able to get the "String" part as well.

A converter is called because it claims to handle a Class, i.e. for an
instance of

============ %< ==========
 class Bar {
   Optional<String> opt = new Optional<String>("foo");
   Object obj = new Optional<String>("bar");
   List<Optional<String>> l =
     Collections.singletonList(new Optional<String>("baz"));
 }
============ %< ==========

your converter would be called 3 times. The ParametrizedType can be provided
only for the instance in the field 'opt'. You would get nothing for the
instance in field 'obj' nor for the one in the list.

>> IMHO you have only two reliably possibilities. Either write the type of
>> the value into an own attribute or use multiple instances of your
>> converter, let them take the type as constructor argument and register
>> those converters as local ones individually.
>
> Thanks for the suggestion. The multiple instances alternative sounds
> doable for me.

Cheers,
Jörg


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Re: Re: Re: Parsing/serializing 'Optional' values

Remko Tronçon
Hi Jörg

> A converter is called because it claims to handle a Class, i.e. for an
> instance of
>
> ============ %< ==========
>  class Bar {
>    Optional<String> opt = new Optional<String>("foo");
>    Object obj = new Optional<String>("bar");
>    List<Optional<String>> l =
>      Collections.singletonList(new Optional<String>("baz"));
>  }
> ============ %< ==========
>
> your converter would be called 3 times. The ParametrizedType can be provided
> only for the instance in the field 'opt'. You would get nothing for the
> instance in field 'obj' nor for the one in the list.

Sure, that's clear. But if you are in the first case (as I am), then
you can't get to the ParameterizedType today from the convertor,
correct?

thanks for all your help,
Remko

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: Re: Re: Re: Re: Parsing/serializing 'Optional' values

Jörg Schaible-2
Hi Remko,

Remko Tronçon wrote:

> Hi Jörg
>
>> A converter is called because it claims to handle a Class, i.e. for an
>> instance of
>>
>> ============ %< ==========
>>  class Bar {
>>    Optional<String> opt = new Optional<String>("foo");
>>    Object obj = new Optional<String>("bar");
>>    List<Optional<String>> l =
>>      Collections.singletonList(new Optional<String>("baz"));
>>  }
>> ============ %< ==========
>>
>> your converter would be called 3 times. The ParametrizedType can be
>> provided only for the instance in the field 'opt'. You would get nothing
>> for the instance in field 'obj' nor for the one in the list.
>
> Sure, that's clear. But if you are in the first case (as I am), then
> you can't get to the ParameterizedType today from the convertor,
> correct?

No. And we cannot change that for now.

Cheers,
Jörg


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email