2

I'm using Newtonsoft JsonConvert.SerializeObject and JsonConvert.DeserializeObject on quite a complicated DTO. Within this, I have some Complex numbers (System.Numerics). Everything works perfectly except for the Complex numbers.

The numbers serialise fine, resulting in:

{
    ... lots of JSON here ...   
    "Electrical": {     
        ... lots of JSON objects ... 
            "Z1": {
                "Real": 0.0017923713150000001,
                "Imaginary": 0.0,
                "Magnitude": 0.0017923713150000001,
                "Phase": 0.0
            },
            "Z0": {
                "Real": 0.0017923713150000001,
                "Imaginary": 0.0,
                "Magnitude": 0.0017923713150000001,
                "Phase": 0.0
            }
        }
    ... lots of JSON .. here ...
}

The problem is in the deserialization, where the complex number returned is full of zeros, for example:

calculation.Electrical.Impedance.Z0
{(0, 0)}
    Imaginary: 0
    Magnitude: 0
    Phase: 0
    Real: 0
    m_imaginary: 0
    m_real: 0

Any suggestions on how to resolve this would be great.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 2
    You might just need to implement a custom `JsonConverter` class. Take a look at the answer here for a similar example: http://stackoverflow.com/questions/24051206/handling-decimal-values-in-newtonsoft-json – awh112 Jan 30 '17 at 21:04

1 Answers1

2

Neither Json.NET nor the JSON standard have a predefined format for complex numbers, and so Json.NET will serialize all the properties of a Complex, generating the output you see. To get clean JSON with just the necessary data, you will need to write a custom JsonConverter that serializes a Complex from and to JSON.

However, what format should be used? Options might include:

  1. As an array: [0.0017923713150000001,0.0].
  2. As an object with "Real" and "Imaginary" properties: {"Real":0.0017923713150000001,"Imaginary":0.0}.
  3. In math.js format: {"mathjs":"Complex","re":0.0017923713150000001,"im":0.0}.

Here are converters for each of these formats:

public abstract class ComplexConverterBase : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Complex) || objectType == typeof(Complex?);
    }
}

public class ComplexArrayConverter : ComplexConverterBase
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var array = serializer.Deserialize<double[]>(reader);
        if (array.Length != 2)
        {
            throw new JsonSerializationException(string.Format("Invalid complex number array of length {0}", array.Length));
        }
        return new Complex(array[0], array[1]);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var complex = (Complex)value;
        writer.WriteStartArray();
        writer.WriteValue(complex.Real);
        writer.WriteValue(complex.Imaginary);
        writer.WriteEndArray();
    }
}

public class ComplexObjectConverter : ComplexConverterBase
{
    // By using a surrogate type, we respect the naming conventions of the serializer's contract resolver.
    class ComplexSurrogate
    {
        public double Real { get; set; }
        public double Imaginary { get; set; }

        public static implicit operator Complex(ComplexSurrogate surrogate)
        {
            if (surrogate == null)
                return default(Complex);
            return new Complex(surrogate.Real, surrogate.Imaginary);
        }

        public static implicit operator ComplexSurrogate(Complex complex)
        {
            return new ComplexSurrogate { Real = complex.Real, Imaginary = complex.Imaginary };
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        return (Complex)serializer.Deserialize<ComplexSurrogate>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, (ComplexSurrogate)(Complex)value);
    }
}

public class ComplexMathJSConverter : ComplexConverterBase
{
    // Serialize in math.js format
    // http://mathjs.org/docs/core/serialization.html
    // By using a surrogate type, we respect the naming conventions of the serializer's contract resolver.
    class ComplexSurrogate
    {
        [JsonProperty(Order = 1)]
        public double re { get; set; }
        [JsonProperty(Order = 2)]
        public double im { get; set; }
        [JsonProperty(Order = 0)]
        public string mathjs { get { return "Complex"; } }

        public static implicit operator Complex(ComplexSurrogate surrogate)
        {
            if (surrogate == null)
                return default(Complex);
            return new Complex(surrogate.re, surrogate.im);
        }

        public static implicit operator ComplexSurrogate(Complex complex)
        {
            return new ComplexSurrogate { re = complex.Real, im = complex.Imaginary };
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        return (Complex)serializer.Deserialize<ComplexSurrogate>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, (ComplexSurrogate)(Complex)value);
    }
}

Then, you would serialize as follows:

var settings = new JsonSerializerSettings
{
    // Add a complex converter to the Converts array.  
    // Use one of ComplexArrayConverter, ComplexMathJSConverter and ComplexObjectConverter
    Converters = { new ComplexArrayConverter() },
};
var json = JsonConvert.SerializeObject(calculation, settings);

To use the converter in global settings, see here for Web API or here when calling the serializer directly.

Sample fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • dbc, thanks for the clear and detailed explanation. It worked perfectly. Your answer was a great help after many hours of searching. – Steven McFadyen Feb 02 '17 at 20:35