// Push the provided state on to our ID stack public static void PushElement(string idJsonProperty, AdaptiveInternalID internalId) { if (internalId.Equals(AdaptiveInternalID.Invalid)) { throw new AdaptiveSerializationException($"Attemping to push an element on to the stack with an invalid ID"); } idStack.Push(new Tuple <string, AdaptiveInternalID, bool>(idJsonProperty, internalId, AdaptiveFallbackConverter.IsInFallback)); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var token = reader.TokenType; switch (token) { case JsonToken.String: { AdaptiveFallbackElement adaptiveFallbackElement = new AdaptiveFallbackElement(); string stringValue = (string)reader.Value; if (stringValue == "drop") { adaptiveFallbackElement.Type = AdaptiveFallbackElement.AdaptiveFallbackType.Drop; } else { throw new AdaptiveSerializationException("The only valid string value for the fallback property is 'drop'."); } return(adaptiveFallbackElement); } case JsonToken.StartObject: { var jObject = JObject.Load(reader); var typeName = AdaptiveTypedElementConverter.GetElementTypeName(objectType, jObject); Type type; if (!AdaptiveTypedElementConverter.TypedElementTypes.Value.TryGetValue(typeName, out type)) { type = typeof(AdaptiveUnknownElement); } IsInFallback = true; string objectId = jObject.Value <string>("id"); AdaptiveInternalID internalID = AdaptiveInternalID.Next(); // Handle deserializing unknown element ParseContext.PushElement(objectId, internalID); var result = (AdaptiveTypedElement)Activator.CreateInstance(type); serializer.Populate(jObject.CreateReader(), result); ParseContext.PopElement(); IsInFallback = false; AdaptiveFallbackElement adaptiveFallbackElement = new AdaptiveFallbackElement(); adaptiveFallbackElement.Type = AdaptiveFallbackElement.AdaptiveFallbackType.Content; adaptiveFallbackElement.Content = result; return(adaptiveFallbackElement); } default: { throw new AdaptiveSerializationException("Invalid value for fallback"); } } }
// Walk stack looking for first element to be marked fallback (which isn't the ID we're supposed to skip), then // return its internal ID. If none, return an invalid ID. (see comment above) public static AdaptiveInternalID GetNearestFallbackID(AdaptiveInternalID skipID) { foreach (var curElement in idStack) { // if element is fallback if (curElement.Item3) { if (!curElement.Item2.Equals(skipID)) { return(curElement.Item2); } } } var invalidID = new AdaptiveInternalID(); return(invalidID); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var jObject = JObject.Load(reader); string typeName = GetElementTypeName(objectType, jObject); if (TypedElementTypes.Value.TryGetValue(typeName, out var type)) { string objectId = jObject.Value <string>("id"); if (objectId == null) { if (typeof(AdaptiveInput).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new AdaptiveSerializationException($"Required property 'id' not found on '{typeName}'"); } } // add id of element to ParseContext AdaptiveInternalID internalID = AdaptiveInternalID.Current(); if (type != typeof(AdaptiveCard)) { internalID = AdaptiveInternalID.Next(); ParseContext.PushElement(objectId, internalID); } var result = (AdaptiveTypedElement)Activator.CreateInstance(type); try { serializer.Populate(jObject.CreateReader(), result); } catch (JsonSerializationException) { } // remove id of element from ParseContext if (type != typeof(AdaptiveCard)) { ParseContext.PopElement(); } HandleAdditionalProperties(result); return(result); } else // We're looking at an unknown element { string objectId = jObject.Value <string>("id"); AdaptiveInternalID internalID = AdaptiveInternalID.Next(); // Handle deserializing unknown element ParseContext.PushElement(objectId, internalID); AdaptiveTypedElement result = null; if (ParseContext.Type == ParseContext.ContextType.Element) { result = (AdaptiveTypedElement)Activator.CreateInstance(typeof(AdaptiveUnknownElement)); serializer.Populate(jObject.CreateReader(), result); } else // ParseContext.Type == ParseContext.ContextType.Action { result = (AdaptiveTypedElement)Activator.CreateInstance(typeof(AdaptiveUnknownAction)); serializer.Populate(jObject.CreateReader(), result); } ParseContext.PopElement(); Warnings.Add(new AdaptiveWarning(-1, $"Unknown element '{typeName}'")); return(result); } }
// Pop the last id off our stack and perform validation public static void PopElement() { // about to pop an element off the stack. perform collision list maintenance and detection. var idsToPop = idStack.Peek(); string elementID = idsToPop.Item1; AdaptiveInternalID elementInternalID = idsToPop.Item2; bool isFallback = idsToPop.Item3; if (!string.IsNullOrEmpty(elementID)) { bool haveCollision = false; var nearestFallbackID = GetNearestFallbackID(elementInternalID); // Walk through the list of elements we've seen with this ID if (elementIds.ContainsKey(elementID)) { foreach (var entryFallBackID in elementIds[elementID]) { // If the element we're about to pop is the fallback parent for this entry, then there's no collision // (fallback content is allowed to have the same ID as its parent) if (entryFallBackID.Equals(elementInternalID)) { haveCollision = false; break; } // The inverse of the above -- if this element's fallback parent is the entry we're looking at, there's // no collision. try { // 0 is the last item on the stack (the one we're about to pop) // 1 is the parent of the last item on the stack var previousInStack = idStack.ElementAt(1); if (previousInStack.Item2.Equals(entryFallBackID)) { // we're looking at a fallback entry for our parent break; } } catch (ArgumentOutOfRangeException) { // we're looking at a toplevel element } // if the element we're inspecting is fallback content, continue on to the next entry if (isFallback) { continue; } // at this point, we may or may not have a collision depending on additional entries. haveCollision = true; } } if (haveCollision) { throw new AdaptiveSerializationException("Collision detected for id '" + elementID + "'"); } // add an entry for this element if it's fallback (we'll add one when we parse it for non-fallback) if (!isFallback) { try { elementIds[elementID].Add(nearestFallbackID); } catch (KeyNotFoundException) { elementIds[elementID] = new List <AdaptiveInternalID>() { nearestFallbackID }; } } } idStack.Pop(); }
public AdaptiveFallbackElement ParseFallback(JToken fallbackJSON, JsonSerializer serializer, string objectId, AdaptiveInternalID internalId) { // Handle fallback as a string ("drop") if (fallbackJSON.Type == JTokenType.String) { var str = fallbackJSON.Value <string>(); if (str == AdaptiveFallbackElement.drop) { // fallback is initialized with "drop" property and empty content return(new AdaptiveFallbackElement(AdaptiveFallbackElement.AdaptiveFallbackType.Drop)); } throw new AdaptiveSerializationException("The only valid string value for the fallback property is 'drop'."); } // handle fallback as an object else if (fallbackJSON.Type == JTokenType.Object) { // fallback value is a JSON object. parse it and add it as fallback content. For more details, refer to // the giant comment on ID collision detection in ParseContext.cpp (ObjectModel). ParseContext.PushElement(objectId, internalId); var elem = new AdaptiveFallbackElement(fallbackJSON.ToObject <AdaptiveTypedElement>()); ParseContext.PopElement(); return(elem); } // Should never get here. Instead should be thrown in CanConvert() throw new AdaptiveSerializationException("Invalid value for fallback"); }