Deserialize Json XYZ Point

2019-09-19 11:56发布

问题:

Autodesk Revit Development

I serialized an XYZ point form a class (Points) from a container (Points and Tags) to a file.

public class Serialize_pack
{
    public View_3D_Data v3ddata;
    public Tag_Class tg;
}

through this Method

public static void serializeme(object classme)
{
    string coor_file = constants.SenddDir() + constants.filename();

    using (StreamWriter file = File.CreateText(coor_file))
    {
        JsonSerializer serializer = new JsonSerializer();

        serializer.Serialize(file, classme);
    }
}

and i got this results

{"Points":{"bboxmin":{"Z":-1000.0,"Y":-100.0,"X":-100.0},"bboxmax":{"Z":-0.1,"Y":100.0,"X":100.0}},"tg":{"eId":"666470","text":"coor: Kiss me"}}

on deserializing i got the results for all the points to a value of (0.0,0.0,0.0) which is a result of unable to parse read values to its propitiate type.

Deserializing Method

public static object deserializeme(string path)
{
    Serialize_pack accquired = null;
    using (StreamReader file = File.OpenText(path))
    {
        JsonSerializer serializer = new JsonSerializer();
        accquired = (Serialize_pack)serializer.Deserialize(file, typeof(Serialize_pack));                           
    }

    return accquired;
}

I wish i could find a way a good way to convert and override this mess.

Edit: Exact Newton.JSon OutPut

{"Points":{"bboxmin":{"Z":-1000.0,"Y":-100.0,"X":-100.0},"bboxmax":{"Z":-0.1,"Y":100.0,"X":100.0},"sboxmin":{"Z":-10.277690406517843,"Y":-13.533464566929133,"X":-13.389107611548557},"sboxmax":{"Z":16.510826771653544,"Y":13.533464566929133,"X":13.389107611548557},"vorEyP":{"Z":30.114082470913921,"Y":34.471718543415037,"X":-7.7202528373680934},"vorFwD":{"Z":-0.57735026918962573,"Y":-0.57735026918962584,"X":0.57735026918962573},"vorUP":{"Z":0.816496580927726,"Y":-0.408248290463863,"X":0.40824829046386296},"v3dname":"Arch_Moustafa-GAJ-145834"},"Tags":{"eId":"666470","origin":{"Z":1154.5239372729186,"Y":1164.3934060532893,"X":-1119.6229882673815},"text":"coor: Kiss me","ledelbo":{"Z":1157.6807845880096,"Y":1163.9955344285622,"X":-1116.8640125770175}}}

Tag Class

public class Tag
{
    public string eId;

    public XYZ origin;
    public string text;
    public XYZ ledelbo;

public void getTagdata(View v)
    {
        ///we need all the annotation to be sent.
       /// do some stuff and cast the results on the public variables

    }
}

Points Class

public class Points
{

    public XYZ bboxmin;
    public XYZ bboxmax;
    public XYZ sboxmin;
    public XYZ sboxmax;

    public XYZ vorEyP;
    public XYZ vorFwD;
    public XYZ vorUP;

    public string v3dname;


    [JsonIgnore]
    public View3D view;

 public void get3dviewdata()
    {
       ///we need all the points to be sent.
       /// do some stuff and cast the results on the public variables
    }
}

回答1:

OK, the problem here seems to be that the XYZ class in Revit is immutable, so the JsonSerializer cannot set the properties. Under normal circumstances the way to deal with this is to decorate an appropriate constructor for your class with JsonConstructorAttribute - but you cannot do that because XYZ is a Revit class, not your own.

Solution #1

To work around this problem, you could subclass XYZ and decorate the appropriate constructor with the attribute - however, I'm not sure whether the Revit class is sealed, or whether this could have unforseen side effects if you actually pass one of these subclased XYXs back to Revit. Alternatively, you could introduce a proxy class purely for serialization and deserialization:

public static class XYZProxyExtensions
{
    public static XYZProxy ToXYZProxy(this XYZ xyz)
    {
        return new XYZProxy(xyz.X, xyz.Y, xyz.Z);
    }
}

public class XYZProxy
{
    public XYZProxy()
    {
        this.X = this.Y = this.Z = 0;
    }

    public XYZProxy(double x, double y, double z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }

    public XYZ ToXYZ()
    {
        return new XYZ(X, Y, Z);
    }
    public override string ToString()
    {
        return string.Format("({0},{1},{2})", X, Y, Z);
    }
}

Having done this, you can then add proxy properties to your custom classes, mark them hidden in the debugger, and tell Json.Net to serialize the proxies, not the original properties:

[JsonObject(MemberSerialization.OptIn)]
public class Points
{
    public XYZ bboxmin { get; set; }

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [JsonProperty(PropertyName = "bboxmin")]
    public XYZProxy bboxminProxy
    {
        get
        {
            return bboxmin.ToXYZProxy();
        }
        set
        {
            bboxmin = value.ToXYZ();
        }
    }
}

More information here: http://www.tecsupra.com/serializing-only-some-properties-of-an-object-to-json-using-newtonsoft-json-net/ and here: How can I change property names when serializing with Json.net?

Solution #2

Alternatively, you might try writing your own JsonConverter for XYZ and then registering it with Json.Net. The converter might look something like this (warning - not tested!)

class XYZConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(XYZ);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = JToken.Load(reader);
        if (obj.Type == JTokenType.Array)
        {
            var arr = (JArray)obj;
            if (arr.Count == 3 && arr.All(token => token.Type == JTokenType.Float))
            {
                return new XYZ(arr[0].Value<double>(), arr[1].Value<double>(), arr[2].Value<double>());
            }
        }
        return null;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var vector = (XYZ)value;
        writer.WriteStartArray();
        writer.WriteValue(vector.X);
        writer.WriteValue(vector.Y);
        writer.WriteValue(vector.Z);
        writer.WriteEndArray();
    }
}

which would produce Json that looks like this:

"bboxmin": [ -100.0, -100.0, -1000.0 ]

This format might be better or worse; are you trying to read and write files to some pre-existing 3rd party library?]

2nd Solution Update

You need to create the JsonSerializer with the appropriate settings in order to invoke the converter, simply doing new JsonSerializer() will not work. I tested the following and it works fine (here my version of your class Points has only 4 fields):

public static class JsonSerializerTest
{
    static JsonSerializerTest()
    {
        // This needs to be done only once, so put it in an appropriate static initializer.
        JsonConvert.DefaultSettings = () => new JsonSerializerSettings
        {
            Converters = new List<JsonConverter> { new XYZConverter() }
        };
    }

    public static void Test()
    {
        Points points = new Points();
        points.bboxmin = new XYZ(-100, -100, -1000);
        points.bboxmax = new XYZ( 100,  100,  1000);
        points.sboxmin = new XYZ(-10, -10, -100);
        points.sboxmax = new XYZ( 10, 10, 100);

        try
        {
            string json;
            using (var writer = new StringWriter())
            {
                JsonSerializer serializer = JsonSerializer.CreateDefault();
                serializer.Serialize(writer, points);
                json = writer.ToString();
            }

            Points newPoints = null;
            using (var reader = new StringReader(json))
            {
                JsonSerializer serializer = JsonSerializer.CreateDefault();
                newPoints = (Points)serializer.Deserialize(reader, typeof(Points));
            }

            Debug.Assert(points.bboxmin.IsAlmostEqualTo(newPoints.bboxmin));
            Debug.Assert(points.bboxmax.IsAlmostEqualTo(newPoints.bboxmax));
            Debug.Assert(points.sboxmin.IsAlmostEqualTo(newPoints.sboxmin));
            Debug.Assert(points.sboxmax.IsAlmostEqualTo(newPoints.sboxmax));
        }
        catch (Exception ex)
        {
            Debug.Assert(false, ex.ToString());
        }
    }
}

The Json output produced is quite simple and readable:

{"bboxmin":[-100.0,-100.0,-1000.0],"bboxmax":[100.0,100.0,1000.0],"sboxmin":[-10.0,-10.0,-100.0],"sboxmax":[10.0,10.0,100.0]}

This avoids the requirement for proxies and so is probably a prettier solution.