1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Collections.Generic;
4 using System.Globalization;
5 using System.IO;
6 using System.Runtime.Serialization.Json;
7 using System.Text;
8 using System.Xml;
9 
10 namespace System.Json
11 {
12     internal static class JXmlToJsonValueConverter
13     {
14         internal const string RootElementName = "root";
15         internal const string ItemElementName = "item";
16         internal const string TypeAttributeName = "type";
17         internal const string ArrayAttributeValue = "array";
18         internal const string BooleanAttributeValue = "boolean";
19         internal const string NullAttributeValue = "null";
20         internal const string NumberAttributeValue = "number";
21         internal const string ObjectAttributeValue = "object";
22         internal const string StringAttributeValue = "string";
23         private const string TypeHintAttributeName = "__type";
24 
25         private static readonly char[] _floatingPointChars = new char[] { '.', 'e', 'E' };
26 
JXMLToJsonValue(Stream jsonStream)27         public static JsonValue JXMLToJsonValue(Stream jsonStream)
28         {
29             if (jsonStream == null)
30             {
31                 throw new ArgumentNullException("jsonStream");
32             }
33 
34             return JXMLToJsonValue(jsonStream, null);
35         }
36 
JXMLToJsonValue(string jsonString)37         public static JsonValue JXMLToJsonValue(string jsonString)
38         {
39             if (jsonString == null)
40             {
41                 throw new ArgumentNullException("jsonString");
42             }
43 
44             if (jsonString.Length == 0)
45             {
46                 throw new ArgumentException(Properties.Resources.JsonStringCannotBeEmpty, "jsonString");
47             }
48 
49             byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);
50 
51             return JXMLToJsonValue(null, jsonBytes);
52         }
53 
JXMLToJsonValue(XmlDictionaryReader jsonReader)54         public static JsonValue JXMLToJsonValue(XmlDictionaryReader jsonReader)
55         {
56             if (jsonReader == null)
57             {
58                 throw new ArgumentNullException("jsonReader");
59             }
60 
61             const string RootObjectName = "RootObject";
62             Stack<JsonValue> jsonStack = new Stack<JsonValue>();
63             string nodeType = null;
64             bool isEmptyElement = false;
65 
66             JsonValue parent = new JsonObject();
67             jsonStack.Push(parent);
68             string currentName = RootObjectName;
69 
70             try
71             {
72                 MoveToRootNode(jsonReader);
73 
74                 while (jsonStack.Count > 0 && jsonReader.NodeType != XmlNodeType.None)
75                 {
76                     if (parent is JsonObject && currentName == null)
77                     {
78                         currentName = GetMemberName(jsonReader);
79                     }
80 
81                     nodeType = jsonReader.GetAttribute(TypeAttributeName) ?? StringAttributeValue;
82 
83                     if (parent is JsonArray)
84                     {
85                         // For arrays, the element name has to be "item"
86                         if (jsonReader.Name != ItemElementName)
87                         {
88                             throw new FormatException(Properties.Resources.IncorrectJsonFormat);
89                         }
90                     }
91 
92                     switch (nodeType)
93                     {
94                         case NullAttributeValue:
95                         case BooleanAttributeValue:
96                         case StringAttributeValue:
97                         case NumberAttributeValue:
98                             JsonPrimitive jsonPrimitive = ReadPrimitive(nodeType, jsonReader);
99                             InsertJsonValue(jsonStack, ref parent, ref currentName, jsonPrimitive, true);
100                             break;
101                         case ArrayAttributeValue:
102                             JsonArray jsonArray = CreateJsonArray(jsonReader, ref isEmptyElement);
103                             InsertJsonValue(jsonStack, ref parent, ref currentName, jsonArray, isEmptyElement);
104                             break;
105                         case ObjectAttributeValue:
106                             JsonObject jsonObject = CreateObjectWithTypeHint(jsonReader, ref isEmptyElement);
107                             InsertJsonValue(jsonStack, ref parent, ref currentName, jsonObject, isEmptyElement);
108                             break;
109                         default:
110                             throw new FormatException(Properties.Resources.IncorrectJsonFormat);
111                     }
112 
113                     while (jsonReader.NodeType == XmlNodeType.EndElement && jsonStack.Count > 0)
114                     {
115                         jsonReader.Read();
116                         SkipWhitespace(jsonReader);
117                         jsonStack.Pop();
118                         if (jsonStack.Count > 0)
119                         {
120                             parent = jsonStack.Peek();
121                         }
122                     }
123                 }
124             }
125             catch (XmlException xmlException)
126             {
127                 throw new FormatException(Properties.Resources.IncorrectJsonFormat, xmlException);
128             }
129 
130             if (jsonStack.Count != 1)
131             {
132                 throw new FormatException(Properties.Resources.IncorrectJsonFormat);
133             }
134 
135             return parent[RootObjectName];
136         }
137 
JXMLToJsonValue(Stream jsonStream, byte[] jsonBytes)138         private static JsonValue JXMLToJsonValue(Stream jsonStream, byte[] jsonBytes)
139         {
140             try
141             {
142                 using (XmlDictionaryReader jsonReader =
143                     jsonStream != null
144                         ? JsonReaderWriterFactory.CreateJsonReader(jsonStream, XmlDictionaryReaderQuotas.Max)
145                         : JsonReaderWriterFactory.CreateJsonReader(jsonBytes, XmlDictionaryReaderQuotas.Max))
146                 {
147                     return JXMLToJsonValue(jsonReader);
148                 }
149             }
150             catch (XmlException)
151             {
152                 throw new FormatException(Properties.Resources.IncorrectJsonFormat);
153             }
154         }
155 
InsertJsonValue(Stack<JsonValue> jsonStack, ref JsonValue parent, ref string currentName, JsonValue jsonValue, bool isEmptyElement)156         private static void InsertJsonValue(Stack<JsonValue> jsonStack, ref JsonValue parent, ref string currentName, JsonValue jsonValue, bool isEmptyElement)
157         {
158             if (parent is JsonArray)
159             {
160                 ((JsonArray)parent).Add(jsonValue);
161             }
162             else
163             {
164                 if (currentName != null)
165                 {
166                     ((JsonObject)parent)[currentName] = jsonValue;
167                     currentName = null;
168                 }
169             }
170 
171             if (!isEmptyElement)
172             {
173                 jsonStack.Push(jsonValue);
174                 parent = jsonValue;
175             }
176         }
177 
GetMemberName(XmlDictionaryReader jsonReader)178         private static string GetMemberName(XmlDictionaryReader jsonReader)
179         {
180             string name;
181             if (jsonReader.NamespaceURI == ItemElementName && jsonReader.LocalName == ItemElementName)
182             {
183                 // JXML special case for names which aren't valid XML names
184                 name = jsonReader.GetAttribute(ItemElementName);
185 
186                 if (name == null)
187                 {
188                     throw new FormatException(Properties.Resources.IncorrectJsonFormat);
189                 }
190             }
191             else
192             {
193                 name = jsonReader.Name;
194             }
195 
196             return name;
197         }
198 
CreateObjectWithTypeHint(XmlDictionaryReader jsonReader, ref bool isEmptyElement)199         private static JsonObject CreateObjectWithTypeHint(XmlDictionaryReader jsonReader, ref bool isEmptyElement)
200         {
201             JsonObject jsonObject = new JsonObject();
202             string typeHintAttribute = jsonReader.GetAttribute(TypeHintAttributeName);
203             isEmptyElement = jsonReader.IsEmptyElement;
204             jsonReader.ReadStartElement();
205             SkipWhitespace(jsonReader);
206 
207             if (typeHintAttribute != null)
208             {
209                 jsonObject.Add(TypeHintAttributeName, typeHintAttribute);
210             }
211 
212             return jsonObject;
213         }
214 
CreateJsonArray(XmlDictionaryReader jsonReader, ref bool isEmptyElement)215         private static JsonArray CreateJsonArray(XmlDictionaryReader jsonReader, ref bool isEmptyElement)
216         {
217             JsonArray jsonArray = new JsonArray();
218             isEmptyElement = jsonReader.IsEmptyElement;
219             jsonReader.ReadStartElement();
220             SkipWhitespace(jsonReader);
221             return jsonArray;
222         }
223 
MoveToRootNode(XmlDictionaryReader jsonReader)224         private static void MoveToRootNode(XmlDictionaryReader jsonReader)
225         {
226             while (!jsonReader.EOF && (jsonReader.NodeType == XmlNodeType.None || jsonReader.NodeType == XmlNodeType.XmlDeclaration))
227             {
228                 // read into <root> node
229                 jsonReader.Read();
230                 SkipWhitespace(jsonReader);
231             }
232 
233             if (jsonReader.NodeType != XmlNodeType.Element || !String.IsNullOrEmpty(jsonReader.NamespaceURI) || jsonReader.Name != RootElementName)
234             {
235                 throw new FormatException(Properties.Resources.IncorrectJsonFormat);
236             }
237         }
238 
ReadPrimitive(string type, XmlDictionaryReader jsonReader)239         private static JsonPrimitive ReadPrimitive(string type, XmlDictionaryReader jsonReader)
240         {
241             JsonValue result = null;
242             switch (type)
243             {
244                 case NullAttributeValue:
245                     jsonReader.Skip();
246                     result = null;
247                     break;
248                 case BooleanAttributeValue:
249                     result = jsonReader.ReadElementContentAsBoolean();
250                     break;
251                 case StringAttributeValue:
252                     result = jsonReader.ReadElementContentAsString();
253                     break;
254                 case NumberAttributeValue:
255                     string temp = jsonReader.ReadElementContentAsString();
256                     result = ConvertStringToJsonNumber(temp);
257                     break;
258             }
259 
260             SkipWhitespace(jsonReader);
261             return (JsonPrimitive)result;
262         }
263 
SkipWhitespace(XmlDictionaryReader reader)264         private static void SkipWhitespace(XmlDictionaryReader reader)
265         {
266             while (!reader.EOF && (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace))
267             {
268                 reader.Read();
269             }
270         }
271 
ConvertStringToJsonNumber(string value)272         private static JsonValue ConvertStringToJsonNumber(string value)
273         {
274             if (value.IndexOfAny(_floatingPointChars) < 0)
275             {
276                 int intVal;
277                 if (Int32.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out intVal))
278                 {
279                     return intVal;
280                 }
281 
282                 long longVal;
283                 if (Int64.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out longVal))
284                 {
285                     return longVal;
286                 }
287             }
288 
289             decimal decValue;
290             if (Decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out decValue) && decValue != 0)
291             {
292                 return decValue;
293             }
294 
295             double dblValue;
296             if (Double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dblValue))
297             {
298                 return dblValue;
299             }
300 
301             throw new ArgumentException(RS.Format(Properties.Resources.InvalidJsonPrimitive, value.ToString()), "value");
302         }
303     }
304 }
305