I am trying to deserialize an abstract class that inherits from MonoBehaviour, I am able to serialize perfectly fine but upon deserialization I get these errors:
You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
and
ArgumentException: The value "null" is not of type "SerializableMeasurement" and cannot be used in this generic collection. Parameter name: value
All measurement types are derived from the abstract Measurement class, then upon serialization converted to their respected derived abstract Serializable Measurement classes which are omitted for now for brevity, they simply add member variables and operations which are unrelated to the issue.
The Measurement class is a type of node in my tree data structure, the serialization and deserialization for the other nodes work as intended.
If you'd like to look at the derived measurement classes and nodes, see the following scripts on my repo:
Other Nodes and Serializers:
Derived Measurements:
Removing the inheritance from MonoBehaviour isn't an option with how the project works.
Example JSON:
{
"name": "Untitled v2",
"subClassId": 1,
"subFields": [
{
"name": "Testing Measurements",
"subClassId": 2,
"measurements": [
{
"measureType": 0,
"length": 0.231667489,
"vertices": [
{
"x": -0.323850036,
"y": -0.338313431,
"z": 0.05710357
},
{
"x": -0.323850065,
"y": -0.228205621,
"z": 0.07805062
},
{
"x": -0.323850065,
"y": -0.2833087,
"z": 0.1503753
},
{
"x": -0.323850036,
"y": -0.229445323,
"z": 0.184519753
},
{
"x": -0.323850036,
"y": -0.3353998,
"z": 0.239965171
}
]
},
{
"pointCenter": {
"x": -0.32385,
"y": -0.2775318,
"z": 0.356699169
},
"surfaceNormal": {
"x": -1.0,
"y": -5.13556273E-08,
"z": 1.39479937E-07
},
"pointRadius": {
"x": -0.32385,
"y": -0.238266677,
"z": 0.432952285
},
"measureType": 1,
"area": 0.02311046
},
{
"point1": {
"x": -0.323850065,
"y": -0.346135437,
"z": 0.4835336
},
"point2": {
"x": -0.323850036,
"y": -0.3418154,
"z": 0.623248458
},
"point3": {
"x": -0.323850065,
"y": -0.228444323,
"z": 0.620691
},
"surfaceNormal": {
"x": -1.0,
"y": -5.13556273E-08,
"z": 1.39479937E-07
},
"measureType": 2,
"area": 0.0158506744
},
{
"measureType": 3,
"area": 0.0491224,
"vertices": [
{
"x": -0.32385,
"y": -0.157076448,
"z": 0.205962881
},
{
"x": -0.32385,
"y": -0.0197166521,
"z": 0.174950376
},
{
"x": -0.32385,
"y": -0.0266412925,
"z": 0.404189438
},
{
"x": -0.32385,
"y": -0.0752793,
"z": 0.418026716
},
{
"x": -0.32385,
"y": -0.126498729,
"z": 0.441788167
},
{
"x": -0.323850036,
"y": -0.158409789,
"z": 0.3402038
},
{
"x": -0.32385,
"y": -0.17995587,
"z": 0.2563426
}
]
}
]
}
]
}
Abstract Measurement Class:
public abstract class Measurement : MonoBehaviour
{
public Vector3 previewPoint;
public bool hidePreviewPoint;
public float quantity;
public string unit;
public abstract void AddPointWithNormal(Vector3 point, Vector3 normal);
protected abstract float CalculateQuantity();
public void SetQuantity()
{
quantity = CalculateQuantity();
}
public int measureType;
public Measurement(int _MeasureType, string _unit)
{
measureType = _MeasureType;
unit = _unit;
}
public abstract SerializableMeasurement GetSerializable();
public abstract void ConstructFromSerializable(SerializableMeasurement sm);
}
Abstract Serializable Measurement Class:
[JsonConverter(typeof(MeasurementConverter))]
public abstract class SerializableMeasurement
{
[JsonProperty(Order = 0)]
public int measureType { get; set; }
public SerializableMeasurement(int measureType)
{
this.measureType = measureType;
}
public abstract string getMeasurementString();
}
Measurement Converter:
public class MeasurementConcreteClassConverter : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (typeof(SerializableMeasurement).IsAssignableFrom(objectType) && !objectType.IsAbstract)
return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow)
return base.ResolveContractConverter(objectType);
}
}
public class MeasurementConverter : JsonConverter
{
static JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new MeasurementConcreteClassConverter() };
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(SerializableMeasurement));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jsonObject = JObject.Load(reader);
// Handles deserialization for each type of measurement
return jsonObject["measureType"].Value<int>() switch
{
0 => JsonConvert.DeserializeObject<Measure_LineSeg>(jsonObject.ToString(), SpecifiedSubclassConversion),
1 => JsonConvert.DeserializeObject<Measure_Circular>(jsonObject.ToString(), SpecifiedSubclassConversion),
2 => JsonConvert.DeserializeObject<Measure_Rectangular>(jsonObject.ToString(), SpecifiedSubclassConversion),
3 => JsonConvert.DeserializeObject<Measure_PolySurface>(jsonObject.ToString(), SpecifiedSubclassConversion),
_ => throw new Exception($"Unknown measurement type {jsonObject["subClassId"].Value<int>()}"),
};
}
}
You are trying to directly serialize to Measure_LineSeg
etc which are MonoBehaviour
components. As the error/warning tells you it makes no sense to create new instances of a MonoBehaviour
without them being attached to any GameObject
.
Removing the inheritance from MonoBehaviour isn't an option with how the project works.
If you create those instances via the constructor it is a very obvious hint that those shouldn't be MonoBehaviour
in the first place! In your case the constructor though seems to rather be a way of trying to solve the deserialization issue.
A MonoBehaviour
shouldn't have any constructor at all as they are created and maintained by the underlying native UnityEngine and the c# instance of it is just an API layer on top of it!
Rather have a method like e.g.
public void Initialize(SerializableMeasurement data)
{
...
}
Then you should rather deserialize into SerializeMeasure_LineSeg
etc
return jsonObject["measureType"].Value<int>() switch
{
0 => JsonConvert.DeserializeObject<SerializeMeasure_LineSeg>(jsonObject.ToString(), SpecifiedSubclassConversion),
1 => JsonConvert.DeserializeObject<SerializeMeasure_Circular>(jsonObject.ToString(), SpecifiedSubclassConversion),
2 => JsonConvert.DeserializeObject<SerializeMeasure_Rectangular>(jsonObject.ToString(), SpecifiedSubclassConversion),
3 => JsonConvert.DeserializeObject<SerializeMeasure_PolySurface>(jsonObject.ToString(), SpecifiedSubclassConversion),
_ => throw new Exception($"Unknown measurement type {jsonObject["subClassId"].Value<int>()}"),
};
which only holds the information and then have a loop running over all of those that spawns the according GameObject
s with according components from prefabs into the scene and initializes them with the given data.
Btw for the type instead of a nothing-saying int
I would probably rather have an
public enum MeasureType : byte
{
LineSeg,
Circular,
Rectangular,
PolySurface
}