public ModelValueChangeEvent(ModelInstance instance, ModelValueProperty property, object oldValue, object newValue) : base(instance) { this.Property = property; if (property.AutoConvert) { this.OldValue = oldValue == null ? null : property.Converter.ConvertTo(oldValue, typeof(object)); this.NewValue = newValue == null ? null : property.Converter.ConvertTo(newValue, typeof(object)); } else { this.OldValue = oldValue; this.NewValue = newValue; } }
static JsonUtility() { serializer = new JsonSerializer { DateParseHandling = DateParseHandling.None, Converters = { new UtcDateTimeConverter() } }; serializableTypes = new HashSet <Type>(); // Register converters for types implementing IJsonSerializable or that have DataContract attributes // Include all types in ExoWeb and ExoRule automatically foreach (var converter in JsonConverter.Infer( typeof(ServiceHandler).Assembly.GetTypes().Union( typeof(Rule).Assembly.GetTypes().Where(type => typeof(Rule).IsAssignableFrom(type))))) { RegisterConverter(converter); } // Deserialize Value Change Event Func <JsonReader, ModelValueChangeEvent> deserializeValueChangeEvent = (reader) => { string p; ModelInstance instance = null; ModelValueProperty property = null; object oldValue = null; object newValue = null; while (reader.ReadProperty(out p)) { switch (p) { case "instance": instance = reader.ReadValue <ModelInstance>(); break; case "property": var propertyName = reader.ReadValue <string>(); property = (ModelValueProperty)instance.Type.Properties[propertyName]; if (property == null) { throw new SerializationException("Unable to deserialize ValueChange: property \"" + propertyName + "\" does not exist for type \"" + instance.Type.Name + "\"."); } break; case "oldValue": if (reader.TokenType == JsonToken.StartObject) { oldValue = reader.ReadValue(property.PropertyType); } else { oldValue = property.CoerceValue(reader.Value); reader.Read(); } break; case "newValue": if (reader.TokenType == JsonToken.StartObject) { newValue = reader.ReadValue(property.PropertyType); } else { newValue = property.CoerceValue(reader.Value); reader.Read(); } break; default: throw new ArgumentException("The specified property could not be deserialized.", p); } } return(new ModelValueChangeEvent(instance, property, oldValue, newValue)); }; // Deserialize Reference Change Event Func <JsonReader, ModelReferenceChangeEvent> deserializeReferenceChangeEvent = (reader) => { string p; ModelInstance instance = null; ModelReferenceProperty property = null; ModelInstance oldValue = null; ModelInstance newValue = null; while (reader.ReadProperty(out p)) { switch (p) { case "instance": instance = reader.ReadValue <ModelInstance>(); break; case "property": var propertyName = reader.ReadValue <string>(); property = (ModelReferenceProperty)instance.Type.Properties[propertyName]; if (property == null) { throw new SerializationException("Unable to deserialize ReferenceChange: property \"" + propertyName + "\" does not exist for type \"" + instance.Type.Name + "\"."); } break; case "oldValue": oldValue = reader.ReadValue <ModelInstance>(); break; case "newValue": newValue = reader.ReadValue <ModelInstance>(); break; default: throw new ArgumentException(@"The specified property could not be deserialized.", p); } } return(new ModelReferenceChangeEvent(instance, property, oldValue, newValue)); }; // Deserialize List Change Event Func <JsonReader, ModelListChangeEvent> deserializeListChangeEvent = (reader) => { string p; ModelInstance instance = null; ModelReferenceProperty property = null; ModelInstance[] added = null; ModelInstance[] removed = null; while (reader.ReadProperty(out p)) { switch (p) { case "instance": instance = reader.ReadValue <ModelInstance>(); break; case "property": var propertyName = reader.ReadValue <string>(); property = (ModelReferenceProperty)instance.Type.Properties[propertyName]; if (property == null) { throw new SerializationException("Unable to deserialize ListChange: property \"" + propertyName + "\" does not exist for type \"" + instance.Type.Name + "\"."); } break; case "added": added = reader.ReadValue <ModelInstance[]>(); break; case "removed": removed = reader.ReadValue <ModelInstance[]>(); break; default: throw new ArgumentException(@"The specified property could not be deserialized.", p); } } return(new ModelListChangeEvent(instance, property, added, removed)); }; // Deserialize Init New Event Func <JsonReader, ModelInitEvent.InitNew> deserializeInitNewEvent = (reader) => { string p; ModelInstance instance = null; while (reader.ReadProperty(out p)) { switch (p) { case "instance": instance = reader.ReadValue <ModelInstance>(); break; default: throw new ArgumentException(@"The specified property could not be deserialized.", p); } } return(new ModelInitEvent.InitNew(instance)); }; // Deserialize Init Existing Event Func <JsonReader, ModelInitEvent.InitExisting> deserializeInitExistingEvent = (reader) => { string p; ModelInstance instance = null; while (reader.ReadProperty(out p)) { switch (p) { case "instance": instance = reader.ReadValue <ModelInstance>(); break; default: throw new ArgumentException(@"The specified property could not be deserialized.", p); } } return(new ModelInitEvent.InitExisting(instance)); }; // Deserialize Delete Event Func <JsonReader, ModelDeleteEvent> deserializeDeleteEvent = (reader) => { string p; ModelInstance instance = null; bool isPendingDelete = false; while (reader.ReadProperty(out p)) { switch (p) { case "instance": instance = reader.ReadValue <ModelInstance>(); break; case "isPendingDelete": isPendingDelete = reader.ReadValue <bool>(); break; default: throw new ArgumentException(@"The specified property could not be deserialized.", p); } } return(new ModelDeleteEvent(instance, isPendingDelete)); }; // Construct Model Instance var createModelInstance = typeof(ModelInstance).GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(ModelType), typeof(string) }, null); // Register custom converters for ModelType, ModelProperty, ModelMethod, ModelInstance, ModelEvent foreach (var converter in new JsonConverter[] { // Model Type new JsonConverter <ModelType>( (modelType, json) => { // Base Type if (modelType.BaseType != null) { json.Set("baseType", JsonConverter.GetJsonReferenceType(modelType.BaseType)); } // Base Type if (!String.IsNullOrEmpty(modelType.Format)) { json.Set("format", modelType.Format); } // Properties if (modelType.Properties.Any()) { json.Set("properties", modelType.Properties .Where(property => property.DeclaringType == modelType && ExoWeb.IncludeInClientModel(property)) .ToDictionary(property => property.Name)); } // Methods if (modelType.Methods.Any()) { json.Set("methods", modelType.Methods.ToDictionary(method => method.Name)); } // Rules var rules = Rule.GetRegisteredRules(modelType).ToList(); var typeRules = rules.Where(rule => (rule.ExecutionLocation & RuleExecutionLocation.Client) > 0 && !(rule is IPropertyRule)).ToArray(); if (typeRules.Any()) { json.Set("rules", typeRules); } // Condition Types var serverConditionTypes = rules .Where(rule => (rule.ExecutionLocation & RuleExecutionLocation.Client) == 0) .SelectMany(rule => rule.ConditionTypes) .ToArray(); if (serverConditionTypes.Any()) { json.Set("conditionTypes", serverConditionTypes); } // Exports var exports = json.Global <Dictionary <string, string> >("exports"); if (exports.Any()) { json.Set("exports", json.Global <Dictionary <string, string> >("exports")); } }, json => { throw new NotSupportedException("ModelType cannot be deserialized."); }), // Model Property new JsonConverter <ModelProperty>( (property, json) => { // Type string type = (property is ModelValueProperty ? JsonConverter.GetJsonValueType(((ModelValueProperty)property).PropertyType) ?? "Object" : JsonConverter.GetJsonReferenceType(((ModelReferenceProperty)property).PropertyType)) + (property.IsList ? "[]" : ""); json.Set("type", type); // IsStatic if (property.IsStatic) { json.Set("isStatic", true); } // IsPersisted if (!property.IsPersisted && !property.IsStatic) { json.Set("isPersisted", false); } // IsCalculated if (property.IsCalculated) { json.Set("isCalculated", true); } // Index int index = 0; foreach (ModelProperty p in property.DeclaringType.Properties) { if (p == property) { break; } if (ExoWeb.IncludeInClientModel(p) && !p.IsStatic) { index++; } } if (!property.IsStatic) { json.Set("index", index); } // Format string format = property.Format; if (!string.IsNullOrEmpty(format)) { json.Set("format", format); } // Label string label = property.Label; if (!string.IsNullOrEmpty(label)) { json.Set("label", label); } // Help Text string helptext = property.HelpText; if (!string.IsNullOrEmpty(helptext)) { json.Set("helptext", helptext); } // Rules var rules = Rule .GetRegisteredRules(property.DeclaringType) .Where(rule => (rule.ExecutionLocation & RuleExecutionLocation.Client) > 0 && rule is IPropertyRule && rule.RootType.Properties[((IPropertyRule)rule).Property] == property) .ToDictionary(rule => rule is ICalculationRule ? "calculated" : String.Format("{0}.{1}.{2}", rule.RootType.Name, ((IPropertyRule)rule).Property, ((IPropertyRule)rule).Name) == rule.Name ? ((IPropertyRule)rule).Name.Substring(0, 1).ToLower() + ((IPropertyRule)rule).Name.Substring(1) : rule.Name); if (rules.Any()) { json.Set("rules", rules); } // Default Value if (property is ModelValueProperty) { var defaultValue = ((ModelValueProperty)property).DefaultValue; if (defaultValue != null) { json.Set("defaultValue", ((ModelValueProperty)property).DefaultValue); } } }, json => { throw new NotSupportedException("ModelProperty cannot be deserialized."); }), // Model Method new JsonConverter <ModelMethod>( (method, json) => { // Parameters json.Set("parameters", method.Parameters.Select(p => p.Name)); // IsStatic json.Set("isStatic", method.IsStatic); }, json => { throw new NotSupportedException("ModelMethod cannot be deserialized."); }), // Model Instance new JsonConverter <ModelInstance>( (instance, json) => { json.Set("id", instance.Id); json.Set("type", instance.Type.Name); }, reader => { string p; ModelType type = null; string id = null; while (reader.ReadProperty(out p)) { switch (p) { case "type": type = ModelContext.Current.GetModelType(reader.ReadValue <string>()); break; case "id": id = reader.ReadValue <string>(); break; // Ignore case "isNew": reader.ReadValue <bool>(); break; default: throw new ArgumentException(@"The specified property could not be deserialized.", p); } } return((ModelInstance)createModelInstance.Invoke(new object[] { type, id })); }), // Model Event new JsonConverter <ModelEvent>( (modelEvent, json) => { throw new NotSupportedException("ModelEvent cannot be deserialized."); }, (reader) => { string p; if (reader.ReadProperty(out p) && p == "type") { string eventName = reader.ReadValue <string>(); switch (eventName) { case "ValueChange": return(deserializeValueChangeEvent(reader)); case "ReferenceChange": return(deserializeReferenceChangeEvent(reader)); case "ListChange": return(deserializeListChangeEvent(reader)); case "InitNew": return(deserializeInitNewEvent(reader)); case "InitExisting": return(deserializeInitExistingEvent(reader)); case "Delete": return(deserializeDeleteEvent(reader)); } throw new NotSupportedException(eventName + " event cannot be deserialized."); } else { throw new FormatException("The type parameter 'type' must be the first serialized value in model event json."); } }), // Model Value Change Event new JsonConverter <ModelValueChangeEvent>( (modelEvent, json) => { //Property sequence matters json.Set("type", "ValueChange"); json.Set("instance", GetEventInstance(modelEvent.Instance, modelEvent.InstanceId)); json.Set("property", modelEvent.Property.Name); json.Set("oldValue", modelEvent.OldValue); json.Set("newValue", modelEvent.NewValue); }, deserializeValueChangeEvent), // Model Reference Change Event new JsonConverter <ModelReferenceChangeEvent>( (modelEvent, json) => { json.Set("type", "ReferenceChange"); json.Set("instance", GetEventInstance(modelEvent.Instance, modelEvent.InstanceId)); json.Set("property", modelEvent.Property.Name); json.Set("oldValue", GetEventInstance(modelEvent.OldValue, modelEvent.OldValueId)); json.Set("newValue", GetEventInstance(modelEvent.NewValue, modelEvent.NewValueId)); }, deserializeReferenceChangeEvent), // Model List Change Event new JsonConverter <ModelListChangeEvent>( (modelEvent, json) => { json.Set("type", "ListChange"); json.Set("instance", GetEventInstance(modelEvent.Instance, modelEvent.InstanceId)); json.Set("property", modelEvent.Property.Name); json.Set("added", modelEvent.Added.Select((instance, index) => GetEventInstance(instance, modelEvent.AddedIds.ElementAt(index)))); json.Set("removed", modelEvent.Removed.Select((instance, index) => GetEventInstance(instance, modelEvent.RemovedIds.ElementAt(index)))); }, deserializeListChangeEvent), // Model Init New Event new JsonConverter <ModelInitEvent.InitNew>( (modelEvent, json) => { json.Set("type", "InitNew"); json.Set("instance", GetEventInstance(modelEvent.Instance, modelEvent.InstanceId)); }, deserializeInitNewEvent), // Model Init Existing Event new JsonConverter <ModelInitEvent.InitExisting>( (modelEvent, json) => { json.Set("type", "InitExisting"); json.Set("instance", GetEventInstance(modelEvent.Instance, modelEvent.InstanceId)); }, deserializeInitExistingEvent), // Model Delete Event new JsonConverter <ModelDeleteEvent>( (modelEvent, json) => { json.Set("type", "Delete"); json.Set("instance", GetEventInstance(modelEvent.Instance, modelEvent.InstanceId)); json.Set("isPendingDelete", modelEvent.IsPendingDelete); }, deserializeDeleteEvent), // Model Save Event new JsonConverter <ModelSaveEvent>( (modelEvent, json) => { json.Set("type", "Save"); json.Set("instance", GetEventInstance(modelEvent.Instance, modelEvent.InstanceId)); json.Set("added", modelEvent.Added.Select(instance => new Dictionary <string, string>() { { "type", instance.Type.Name }, { "oldId", instance.OriginalId }, { "newId", instance.Id } })); json.Set("modified", modelEvent.Modified); json.Set("deleted", modelEvent.Deleted); }, json => { throw new NotSupportedException("ModelSaveEvent cannot be deserialized."); }), // Condition Type new JsonConverter <ConditionType>( (conditionType, json) => { json.Set("code", conditionType.Code); json.Set("category", conditionType.Category.ToString()); if (conditionType.Sets != null && conditionType.Sets.Any()) { json.Set("sets", conditionType.Sets.Select(set => set.Name)); } json.Set("message", conditionType.Message); }, json => { throw new NotSupportedException("ConditionType cannot be deserialized."); }), // Condition new JsonConverter <Condition>( (condition, json) => { if (condition.Message != condition.Type.Message) { json.Set("message", condition.Message); } json.Set("targets", condition.Targets.Where(ct => ct.Target != null)); }, json => { throw new NotSupportedException("Condition cannot be deserialized."); }), // Condition Target new JsonConverter <ConditionTarget>( (conditionTarget, json) => { json.Set("instance", conditionTarget.Target); json.Set("properties", conditionTarget.Properties); }, json => { throw new NotSupportedException("ConditionTarget cannot be deserialized."); }), // Rule new JsonConverter <Rule>( (rule, json) => { if (rule is ICalculationRule) { var calculation = (ICalculationRule)rule; json.Set("onChangeOf", calculation.Predicates); if (rule is IClientCalculationRule) { var clientCalc = (IClientCalculationRule)rule; json.Set("calculate", clientCalc.FunctionBody); foreach (var export in clientCalc.Exports) { json.Global <Dictionary <string, string> >("exports")[export.Key] = export.Value; } } else { json.Set("calculate", calculation.Calculation); } } else if (rule is IConditionRule) { var condition = (IConditionRule)rule; json.Set("type", "condition"); json.Set("properties", condition.Properties); json.Set("onChangeOf", condition.Predicates); json.Set("conditionType", condition.ConditionType); json.Set("assert", condition.Condition); } else { throw new NotSupportedException("Rules of type " + rule.GetType().FullName + " cannot be serialized. Call ExoWeb.RegisterConverters() to register a converter to support serializing rules of this type."); } }, json => { throw new NotSupportedException("Rule cannot be deserialized."); }), // AllowedValuesRule new JsonConverter <AllowedValuesRule>( (rule, json) => { SerializePropertyRule(rule, json); var sourceExpression = rule.SourceExpression; if (sourceExpression != null) { json.Set("fn", sourceExpression.Expression); if (!String.IsNullOrEmpty(rule.Path)) { json.Set("onChangeOf", new string[] { rule.Path }); } } else { json.Set("source", rule.Source); } if (rule.IgnoreValidation) { json.Set("ignoreValidation", rule.IgnoreValidation); } }, json => { throw new NotSupportedException("AllowedValuesRule cannot be deserialized."); }), // CompareRule new JsonConverter <CompareRule>( (rule, json) => { SerializePropertyRule(rule, json); json.Set("compareOperator", rule.CompareOperator.ToString()); json.Set("compareSource", rule.CompareSource); }, json => { throw new NotSupportedException("CompareRule cannot be deserialized."); }), // ListLengthRule new JsonConverter <ListLengthRule>( (rule, json) => { SerializePropertyRule(rule, json); // Min if (rule.MinExpression != null) { json.Set("minFn", rule.MinExpression.Expression); } else { json.Set("min", rule.Minimum); } // Max if (rule.MaxExpression != null) { json.Set("maxFn", rule.MaxExpression.Expression); } else { json.Set("max", rule.Maximum); } // OnChangeOf if (!String.IsNullOrEmpty(rule.Path)) { json.Set("onChangeOf", new string[] { rule.Path }); } }, json => { throw new NotSupportedException("ListLengthRule cannot be deserialized."); }), // RangeRule new JsonConverter <RangeRule>( (rule, json) => { SerializePropertyRule(rule, json); // Min if (rule.MinExpression != null) { json.Set("minFn", rule.MinExpression.Expression); } else { json.Set("min", rule.Minimum); } // Max if (rule.MaxExpression != null) { json.Set("maxFn", rule.MaxExpression.Expression); } else { json.Set("max", rule.Maximum); } // OnChangeOf if (!String.IsNullOrEmpty(rule.Path)) { json.Set("onChangeOf", new string[] { rule.Path }); } }, json => { throw new NotSupportedException("RangeRule cannot be deserialized."); }), // ValidationRule new JsonConverter <ValidationRule>( (rule, json) => { SerializePropertyRule(rule, json); // Validation json.Set("isError", rule.ValidationExpression.Expression); // ErrorMessage if (rule.ErrorMessageResource != null) { json.Set("message", rule.ErrorMessageResource); } else { json.Set("message", rule.ErrorMessageExpression.Expression); } json.Set("properties", rule.AdditionalTargets); // OnChangeOf if (!String.IsNullOrEmpty(rule.Path)) { json.Set("onChangeOf", new string[] { rule.Path }); } }, json => { throw new NotSupportedException("ValidationRule cannot be deserialized."); }), // RequiredRule new JsonConverter <RequiredRule>( (rule, json) => { SerializePropertyRule(rule, json); if (rule.RequiredValue != null) { json.Set("requiredValue", rule.RequiredValue); } }, json => { throw new NotSupportedException("RequiredRule cannot be deserialized."); }), // RequiredIfRule new JsonConverter <RequiredIfRule>( (rule, json) => { SerializePropertyRule(rule, json); if (rule.RequiredExpression != null) { json.Set("fn", rule.RequiredExpression.Expression); // OnChangeOf if (!String.IsNullOrEmpty(rule.Path)) { json.Set("onChangeOf", new string[] { rule.Path }); } } else { json.Set("compareOperator", rule.CompareOperator.ToString()); json.Set("compareSource", rule.CompareSource); json.Set("compareValue", rule.CompareValue); } if (rule.RequiredValue != null) { json.Set("requiredValue", rule.RequiredValue); } }, json => { throw new NotSupportedException("RequiredIfRule cannot be deserialized."); }), // OwnerRule new JsonConverter <OwnerRule>( (rule, json) => SerializePropertyRule(rule, json), json => { throw new NotSupportedException("OwnerRule cannot be deserialized."); }), // StringLengthRule new JsonConverter <StringLengthRule>( (rule, json) => { SerializePropertyRule(rule, json); if (rule.Minimum > 0) { json.Set("min", rule.Minimum); } if (rule.Maximum > 0) { json.Set("max", rule.Maximum); } }, json => { throw new NotSupportedException("StringLengthRule cannot be deserialized."); }), // StringFormatRule new JsonConverter <StringFormatRule>( (rule, json) => { SerializePropertyRule(rule, json); if (!String.IsNullOrEmpty(rule.FormatDescription)) { json.Set("description", rule.FormatDescription); } json.Set("expression", rule.FormatExpression.ToString()); if (!String.IsNullOrEmpty(rule.ReformatExpression)) { json.Set("reformat", rule.ReformatExpression); } }, json => { throw new NotSupportedException("StringFormatRule cannot be deserialized."); }), }) { JsonUtility.RegisterConverter(converter); } // Cache the method info of the deserialize method // The non-generic version of this method was added in .NET 4.0 deserialize = serializer.GetType().GetMethod("Deserialize", new Type[] { typeof(string) }); }
protected override object GetMissingPropertyValue(string jsPropertyName) { // special meta property if (jsPropertyName == "meta") { return(LazyDefineProperty("meta", new Meta(Engine, RealObject))); } // handle model properties string modelPropertyName; if (jsPropertyName.StartsWith(GetterPrefix)) { modelPropertyName = jsPropertyName.Substring(GetterPrefix.Length); } else if (jsPropertyName.StartsWith(SetterPrefix)) { throw new InvalidOperationException("Properties are read-only"); } else { throw new InvalidOperationException("Only property get accessors are supported on model objects: " + jsPropertyName); } ModelProperty property = RealObject.Type.Properties[modelPropertyName]; if (property == null) { throw new InvalidPropertyException(RealObject.Type, modelPropertyName); } if (property is ModelValueProperty) { // optimization: cast outside of delegate ModelValueProperty valueProperty = (ModelValueProperty)property; return(LazyDefineMethod(jsPropertyName, instance => { var value = instance.GetValue(valueProperty); if (value is decimal) { return decimal.ToDouble((decimal)value); } if (value is float) { return Convert.ToDouble((float)value); } if (value is long) { return Convert.ToInt32((long)value); } if (value is DateTime) { var dateTime = (DateTime)value; // Use the same serialization format as `JsonUtility.UtcDateTimeConverter`. var dateString = dateTime.ToUniversalTime().ToString(@"yyyy-MM-dd\THH:mm:ss.fff\Z"); var jsDate = Engine.Date.Construct(dateString); return jsDate; } return value; })); } ModelReferenceProperty refProperty = (ModelReferenceProperty)property; if (refProperty.IsList) { return(LazyDefineMethod(jsPropertyName, instance => { return factory.Wrap(instance.GetList(refProperty)); })); } else { return(LazyDefineMethod(jsPropertyName, instance => { return factory.Wrap(instance.GetReference(refProperty)); })); } }