Created
June 12, 2016 12:16
-
-
Save alastairtree/8dac89d554ad8f8b9ef3accaa8cc2127 to your computer and use it in GitHub Desktop.
xml and json conversion formatters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Xml; | |
using Newtonsoft.Json.Linq; | |
namespace Formatters | |
{ | |
public class XmlDocumentCleanJsonFormatter : XmlDocumentRawJsonFormatter | |
{ | |
Dictionary<string,string> arrayNameMapping = new Dictionary<string, string>(); | |
private readonly string arrayPrefix = "ArrayOf"; | |
protected override string TransformXmlDocument(XmlDocument doc) | |
{ | |
var jsonString = base.TransformXmlDocument(doc); | |
var tokenisedJsonDocument = JToken.Parse(jsonString); | |
RecursivelyWalkDown(tokenisedJsonDocument, CleanEachNodeIfItHasRedundantArraySerialisationFormatting); | |
var unwrappedRootToken = UnwrapOuterToken(tokenisedJsonDocument); | |
var cleanedUpArrayProperties = TidyUpArrayOfNamedProperties(unwrappedRootToken); | |
var camelCasedTokenisedDocument = ToCamelCasedProperJsonDocument(cleanedUpArrayProperties); | |
var text = camelCasedTokenisedDocument.ToString(JsonFormatting); | |
return text; | |
} | |
private JToken UnwrapOuterToken(JToken jToken) | |
{ | |
var jobject = jToken as JObject; | |
if (jobject != null && NodeHasASinglePropertyThatIsAnObject(jobject)) | |
{ | |
return jobject.Properties().Single(n => n.Name != "?xml").Value; | |
} | |
return jToken; | |
} | |
/// <summary> | |
/// camel case all properties and remove any illegal @ suffixed properties | |
/// </summary> | |
/// <param name="tokenisedJsonDocument"></param> | |
/// <returns></returns> | |
private static JToken ToCamelCasedProperJsonDocument(JToken tokenisedJsonDocument) | |
{ | |
Func<string, string> camelCaser = name => | |
{ | |
name = name.TrimStart("@".ToArray()); | |
return name[0].ToString().ToLower() + | |
name.Substring(1, name.Length - 1) | |
; | |
}; | |
var camelCasedTokenisedDocument = Rename(tokenisedJsonDocument, camelCaser); | |
return camelCasedTokenisedDocument; | |
} | |
public static JToken Rename(JToken json, Func<string, string> map) | |
{ | |
var property = json as JProperty; | |
if (property != null) | |
{ | |
return new JProperty(map(property.Name), Rename(property.Value, map)); | |
} | |
var array = json as JArray; | |
if (array != null) | |
{ | |
var cont = array.Select(el => Rename(el, map)); | |
return new JArray(cont); | |
} | |
var jObject = json as JObject; | |
if (jObject != null) | |
{ | |
var cont = jObject.Properties().Select(el => Rename(el, map)); | |
return new JObject(cont); | |
} | |
return json; | |
} | |
public JToken TidyUpArrayOfNamedProperties(JToken json) | |
{ | |
var property = json as JProperty; | |
if (property != null) | |
{ | |
if(property.Name.StartsWith(arrayPrefix) && property.Value is JArray && arrayNameMapping.ContainsKey(property.Name)) | |
return new JProperty( | |
arrayNameMapping[property.Name], TidyUpArrayOfNamedProperties(property.Value)); | |
else | |
return new JProperty(property.Name, TidyUpArrayOfNamedProperties(property.Value)); | |
} | |
var array = json as JArray; | |
if (array != null) | |
{ | |
var cont = array.Select(TidyUpArrayOfNamedProperties); | |
return new JArray(cont); | |
} | |
var jObject = json as JObject; | |
if (jObject != null) | |
{ | |
var cont = jObject.Properties().Select(TidyUpArrayOfNamedProperties); | |
return new JObject(cont); | |
} | |
return json; | |
} | |
private bool CleanEachNodeIfItHasRedundantArraySerialisationFormatting(JObject node) | |
{ | |
if (NodesParentIsProperty(node) && | |
NodeHasASinglePropertyThatIsAnArray(node)) | |
{ | |
var parentProperty = ((JProperty) node.Parent); | |
var singleChildProperty = node.Properties().Single(WhereNotIgnoredNode); | |
if (PropertiesAreNamedPluralAndSingularParentAndChild(parentProperty, singleChildProperty) | |
|| PropertiesAreNamedArrayOfParentAndChildPair(parentProperty, singleChildProperty)) | |
{ | |
ThenRemoveTheRedundantChildNodeAndReplaceWithArrayContents(singleChildProperty, parentProperty); | |
ReattachIgnoredProperties(node, parentProperty); | |
return true; | |
} | |
} | |
if (NodesParentIsProperty(node) && | |
NodeHasASinglePropertyThatIsAnObject(node)) | |
{ | |
var parentProperty = ((JProperty) node.Parent); | |
var singleChildProperty = node.Properties().Single(WhereNotIgnoredNode); | |
if (PropertiesAreNamedPluralAndSingularParentAndChild(parentProperty, singleChildProperty) | |
|| PropertiesAreNamedArrayOfParentAndChildPair(parentProperty, singleChildProperty)) | |
{ | |
ThenRemoveTheRedundantChildNodeAndReplaceWithNewArrayContainingTheSoloChild(singleChildProperty, | |
parentProperty); | |
ReattachIgnoredProperties(node, parentProperty); | |
return true; | |
} | |
} | |
return false; | |
} | |
private static void ReattachIgnoredProperties(JObject node, JProperty parentProperty) | |
{ | |
//we may have ignored xml attributes which become @attributeName json properties so recreate them | |
foreach (var xmlAttributeNodes in node.Properties().Where(WhereIgnoredNode)) | |
{ | |
parentProperty.AddBeforeSelf(xmlAttributeNodes.DeepClone()); | |
} | |
} | |
private static bool WhereNotIgnoredNode(JProperty node) | |
{ | |
return !WhereIgnoredNode(node); | |
} | |
private static bool WhereIgnoredNode(JProperty node) | |
{ | |
return node.Name.StartsWith("@") || node.Name == "?xml"; | |
} | |
private static void ThenRemoveTheRedundantChildNodeAndReplaceWithArrayContents(JProperty singleChildProperty, | |
JProperty parentProperty) | |
{ | |
parentProperty.Value = singleChildProperty.Value.DeepClone(); | |
} | |
private static void ThenRemoveTheRedundantChildNodeAndReplaceWithNewArrayContainingTheSoloChild( | |
JProperty singleChildProperty, | |
JProperty parentProperty) | |
{ | |
parentProperty.Value = new JArray(singleChildProperty.Value.DeepClone()); | |
} | |
private static bool NodesParentIsProperty(JObject node) | |
{ | |
return (node.Parent as JProperty) != null; | |
} | |
private static bool NodeHasASinglePropertyThatIsAnArray(JObject node) | |
{ | |
var props = node.Properties().Where(WhereNotIgnoredNode).ToArray(); | |
return props.Count() == 1 && | |
props.Single().Value.Type == JTokenType.Array; | |
} | |
private static bool NodeHasASinglePropertyThatIsAnObject(JObject node) | |
{ | |
var props = node.Properties().Where(WhereNotIgnoredNode).ToArray(); | |
return props.Count() == 1 && props.Single().Value.Type == JTokenType.Object; | |
} | |
private static bool PropertiesAreNamedPluralAndSingularParentAndChild(JProperty parent, JProperty child) | |
{ | |
//this is not a perfect algorithm at spotting plurals but works well in practice | |
return parent.Name.Substring(0, 4) == child.Name.Substring(0, 4) && | |
parent.Name.EndsWith("s"); | |
} | |
private bool PropertiesAreNamedArrayOfParentAndChildPair(JProperty parent, JProperty child) | |
{ | |
var parentName = parent.Name.ToLower(); | |
if (parentName.StartsWith(arrayPrefix.ToLower())) | |
{ | |
var childName = child.Name.ToLower(); | |
var childCutoffPoint = childName.Length - 2; // handles s/es/is/ies pluralisation | |
childName = childName.Substring(0, childCutoffPoint); | |
//this is not a perfect algorithm at spotting plurals but works well in practice | |
var match = parentName.Contains(arrayPrefix.ToLower() + childName); | |
if (match) | |
{ | |
// save the naming of the child for later cleanup | |
var pluralisedChild = child.Name.EndsWith("s") ? child.Name : (child.Name + "s"); | |
if(!arrayNameMapping.ContainsKey(parent.Name)) | |
arrayNameMapping.Add(parent.Name, pluralisedChild); | |
return true; | |
} | |
} | |
return false; | |
} | |
private static void RecursivelyWalkDown(JToken node, | |
Func<JObject, bool> objectAction) | |
{ | |
if (node.Type == JTokenType.Object) | |
{ | |
foreach (var jProperty in node.Children<JProperty>().ToList()) | |
{ | |
RecursivelyWalkDown(jProperty.Value, objectAction); | |
} | |
objectAction((JObject) node); | |
} | |
else if (node.Type == JTokenType.Array) | |
{ | |
foreach (var child in node.Children().ToList()) | |
{ | |
RecursivelyWalkDown(child, objectAction); | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Configuration; | |
using System.IO; | |
using System.Net.Http; | |
using System.Net.Http.Formatting; | |
using System.Net.Http.Headers; | |
using System.Xml; | |
using Newtonsoft.Json; | |
using Formatting = Newtonsoft.Json.Formatting; | |
namespace Formatters | |
{ | |
public class XmlDocumentRawJsonFormatter : BufferedMediaTypeFormatter | |
{ | |
protected static readonly Formatting JsonFormatting = | |
ConfigurationManager.AppSettings["json-indented"]?.Equals("true") ?? false ? Formatting.Indented : Formatting.None; | |
public XmlDocumentRawJsonFormatter() | |
{ | |
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); | |
} | |
public override bool CanWriteType(Type type) | |
{ | |
if (type == typeof (XmlDocument)) | |
{ | |
return true; | |
} | |
return false; | |
} | |
public override bool CanReadType(Type type) | |
{ | |
return false; | |
} | |
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content) | |
{ | |
using (var writer = new StreamWriter(writeStream)) | |
{ | |
var doc = ((XmlDocument) value); | |
var jsonString = TransformXmlDocument(doc); | |
writer.Write(jsonString); | |
} | |
} | |
protected virtual string TransformXmlDocument(XmlDocument doc) | |
{ | |
return ConvertXmlToJson(doc); | |
} | |
private static string ConvertXmlToJson(XmlDocument doc) | |
{ | |
JsonConvert.DefaultSettings = () => WebApiConfig.DefaultJsonSerializerSettings; | |
var jsonString = JsonConvert.SerializeObject(doc, JsonFormatting, | |
WebApiConfig.DefaultJsonSerializerSettings); | |
return jsonString; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Net.Http; | |
using System.Net.Http.Formatting; | |
using System.Net.Http.Headers; | |
using System.Xml; | |
using System.Xml.Linq; | |
namespace Formatters | |
{ | |
public class XmlDocumentXmlFormatter : BufferedMediaTypeFormatter | |
{ | |
public XmlDocumentXmlFormatter() | |
{ | |
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml")); | |
} | |
public override bool CanWriteType(Type type) | |
{ | |
if (type == typeof (XmlDocument)) | |
{ | |
return true; | |
} | |
return false; | |
} | |
public override bool CanReadType(Type type) | |
{ | |
return false; | |
} | |
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content) | |
{ | |
using (var writer = new StreamWriter(writeStream)) | |
{ | |
((XmlDocument) value).Save(writer); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment