Search code examples
snakeyamlyamldotnet

Handle Double.NaN from Java to C# using snakeyaml (Java) and YamlDotNet (C#)


I'm using YAML to communicate between C# GUI and server side Java, which is working fine in general. However, if I pass a field that is a Double and the value is Double.NaN on Java side the Yaml passes as ".NaN", and when I come to deserialize on the C# side a 'System.FormatException' is thrown as C# expects the string "NaN" [not ".NaN"].

Does anyone know if there is a way to intercept the deserializer, or add formatting so that on the C# side ".NaN" can be parsed in a double?

(One workaround I can think of is changing all NaN's to a special value before serliazing to YAML, and then on C# recognizing the special value and converting back to NaN, but this seems like a big hack.)


Solution

  • It seems that this is a bug in the way YamlDotNet handles floats. Until it is fixed, you can work around it by registering a custom node INodeDeserializer that will handle these special cases.

    Here is a quick-and-dirty implementation of such a deserializer:

    public class FloatNodeDeserializer : INodeDeserializer
    {
        private static readonly Dictionary<Tuple<Type, string>, object> SpecialFloats =
            new Dictionary<Tuple<Type, string>, object>
        {
            { Tuple.Create(typeof(float), ".nan"), float.NaN },
            { Tuple.Create(typeof(float), ".inf"), float.PositiveInfinity },
            { Tuple.Create(typeof(float), "-.inf"), float.NegativeInfinity },
    
            { Tuple.Create(typeof(double), ".nan"), double.NaN },
            { Tuple.Create(typeof(double), ".inf"), double.PositiveInfinity },
            { Tuple.Create(typeof(double), "-.inf"), double.NegativeInfinity },
        };
    
        bool INodeDeserializer.Deserialize(
            EventReader reader,
            Type expectedType,
            Func<EventReader, Type, object> nestedObjectDeserializer,
            out object value
        ) {
            var scalar = reader.Peek<Scalar>();
            if (scalar == null) {
                value = null;
                return false;
            }
    
            var found = SpecialFloats.TryGetValue(
                Tuple.Create(expectedType, scalar.Value),
                out value);
    
            if(found) {
                reader.Allow<Scalar>();
            }
            return found;
        }
    }
    

    The way to register it is:

    var deserializer = new Deserializer();
    deserializer.NodeDeserializers.Insert(0, new FloatNodeDeserializer());
    

    See a fully working fiddle here