Prevent Flex numeric nulls from turning to zeros in BlazeDS

This question we get on almost every new client project: “We’re struggling with handling of null values for numeric data types in Flex/Java projects. Every time there’s an update, we end up replacing the original nulls with zeros when the user didn’t actually change that value.  Have you guys come up with a silver bullet for handling numeric nulls?”

Consider a Java method with a Double parameter. You pass uninitialized ActionScript Number, that is, Number.NaN. What will BlazeDS deserialize (unmarshal)? Double.NaN. At this point your Java code may use something like (value==Double.NaN)?null:value, capitalizing on the fact that information about special value of NaN had been delivered from your client code to your server code. Now, let’s assume you change the signature of the Java method to accept Long instead of Double. You pass NaN and you get … 0! The same happens with marshaling ActionScript object that carries Number.NaN properties: they turn into 0, if, god forbid, their Java counterparties are declared as Long or Integer.

The reason is that while Java has Double.NaN it does not support either Long.NaN or Integer.NaN. Looking at the following snippet of code you can see how differently Double and Long variables get treated by Java:


     Double dbl = new Double(Double.NaN); // We emulate incoming numeric null
     System.out.println(dbl); //prints NaN, cause dbl "knows" it came from null
     long l = new Long(dbl.longValue(new Double(Double.NaN)));
     System.out.println(l); // Oops, prints 0!

Unfortunately, native BlazeDS flex.messaging.io.amf.translator.NumberDecoder falls into this trap. So, what is to be done? Luckily, BlazeDS is an open source product and this class has to be slightly modified.

Figure 1 illustrates the changes required to protect null-ability of your Long, Integer, etc values, except Double (we explain what to do with Double-s a bit later):

Modified NumberDecoder class  under Deltawalker

Figure 1. Modified NumberDecoder. Farata modification is on the left, canonical BlazeDS class is on the right.

Now, after you make the changes and compile the class against the rest of the flex-messaging-*** jars you can re-jar your own flex-messaging-core.jar.
Better yer, place this class in the common server folder so that it positively affects classpath of all applications on the server.
From now on, Number.NaN will come as Long null, or Integer null – whatever you decide on the Java side.

It this sounds like to big of a deal to you, keep using Double values and convert Double.NaN to null yourself, when appropriate.


Finally, if you would also like to see Double.NaN automatically converted to null, you will have to substitute one more BlazeDS class – flex.messaging.io.amf.translator.NativeDecoder. Explanation: it’s just so happens that BlazeDS marshalling ignores the NumberDecoder when the source (Number, aka Double) and target (Double) types are the same. Here we come and force BlazeDS to use NumberDecoder with numbers no matter what:

    public class NativeDecoder extends ActionScriptDecoder
{
    public Object decodeObject(Object shell, Object encodedObject, Class desiredClass)
    {
       if ( DecoderFactory.isNumber(desiredClass)){
         NumberDecoder numberDecoder = new NumberDecoder();
            return numberDecoder.decodeObject(encodedObject, desiredClass);
       } else
         return encodedObject;      // the sole original BlazeDS line
    }
}

Source code:
NumberDecoder.java modified by Farata
NativeDecoder.java modified by Farata

I’d like to use this opportunity and invite Flex developers living in Europe to attend our Advanced Flex Master Class in Brussels, Belgium on March 1 and 2, 2010.

Victor Rasputnis

7 thoughts on “Prevent Flex numeric nulls from turning to zeros in BlazeDS

  1. Is there other way to accomplish this without rebuilding blazeDS?
    I have seen there is a class ASTranslator, could this class serve to this purpose?
    Maybe extending from ASTranslator and registering the new translator in services-config.xml?

    Nono

    • Nono,

      Please read my next post: rebuild of the BlazeDS can be avoided by using so called Custom Marshaller.
      In that case you would resolve the issue with two classes: CustomMarshaller, that gets configured per Endpoint in the services-config.xml and NumberDecoder.

      WBR,
      Victor

  2. Hi, Thanks!!

    Is it support both ways? means, from the Java to AS and from the AS to Java ?

    i understand that it simply upgrade the decoder , but what about the encoder?

    Ziv

  3. Hi Victor,

    I have implemented your solution and it works like a charm. Thanks a lot!

    This seems to be a big bug with BlazeDS, so I hope they will fix it soon.

    Kim

  4. While the article addresses null values coming from Flex to Java we have taken one step further to make sure that nulls going from Java to Flex are being sent as NaN as well.

    We just have to change the file flex.messaging.io.amf.Amf3Output.java in BlazeDS source. All we are doing here is when an object is of type Long,Double,Integer,Float or Short and is null we convert the return value to Double.NaN in the DataOutputStream. This ensures that Flex gets Double.NaN on the receiving end. Note: The same change below should be made to flex.messaging.io.amf.Amf0Output.java if you are using the AMF0 format for ActionScript 1.0 and 2.0.

    Class Amf3Output.java

    protected void writePropertyProxy(PropertyProxy proxy, Object instance) throws IOException
    {

    else if (propertyNames != null)
    {
    Iterator it = propertyNames.iterator();
    while (it.hasNext())
    {
    String propName = (String)it.next();
    Object value = null;
    value = proxy.getValue(instance, propName);

    // IDS – Start of changes – 01/10/2010
    value = proxy.getValue(instance, propName);
    if (IdsObjectHelper.isNullNumber(instance,propName,value))
    writeAMFDouble(Double.NaN);
    else
    // IDS – End of Changes – 01/10/2010
    writeObjectProperty(propName, value);

    }
    }

    writeObjectEnd();
    }

    And the helper class:

    package com.ids.foundation.flex.messaging.io;

    import java.lang.reflect.Field;

    public class IdsObjectHelper {

    static boolean useCustomLogic = true;

    public static boolean isNullNumber(Object object, String propName, Object value) {
    Field[] fields = object.getClass().getDeclaredFields();

    if (value != null || !useCustomLogic)
    return false;

    for (int index = 0; index < fields.length; index++) {
    if (fields[index].getName().equals(propName)) {
    if (fields[index].getType().isInstance(new Integer(0)))
    return true;
    else if (fields[index].getType().isInstance(new Double(0)))
    return true;
    else if (fields[index].getType().isInstance(new Long(0)))
    return true;
    else if (fields[index].getType().isInstance(new Float(0)))
    return true;
    else if (fields[index].getType().isInstance(new Short((short)0)))
    return true;
    else
    return false;
    }
    }
    return false;
    }

    }

Comments are closed.