Autodesk Revit Development
I serialized an XYZ point form a class (Points) from a container (Points and Tags) to a file.
public class S
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.