суббота, 2 июня 2012 г.

Парсинг Json на C# / Песочница / Хабрахабр

Парсинг Json на C#

Прикручивая авторизацию с помощью популярных сайтов я столкнулся с проблемой. Согласно стандарта OAuth ответ от сервера авторизации приходит в формате Json, а в .net нет удобной функции для его парсинга. Существует конечно JavaScriptSerializer но он наследует все проблемы сериализеров. Во первых, если будет расхождение в названии полей, тогда будут появляться исключения. Во вторых чисто лень создавать класс, пусть и содержащий только название полей, под каждый ответ. Я подключал 8 систем авторизации, в среднем мне пришлось бы написать по 2-3 класса на каждую. Т.е. где то 20 классов. Я уже не говорю о тех, кто делает глубокую интеграцию. Зачем нагромождать код? Ну и естественно в веб особо остро стоит вопрос производительности, а сериализаторы производительностью не блещут. Исходя из всех этих рассуждений я написал довольно простую функцию, которая превращает строку json в словарь типа Dictionary<string,string>.

        static Dictionary<string, string> ParseJson(string res)
        {
            var lines = res.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            var ht = new Dictionary<string, string>(20);
            var st = new Stack<string>(20);

            for (int i = 0; i < lines.Length; ++i)
            {
                var line = lines[i];
                var pair = line.Split(":".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries);

                if (pair.Length == 2)
                {
                    var key = ClearString(pair[0]);
                    var val = ClearString(pair[1]);

                    if (val == "{")
                    {
                        st.Push(key);
                    }
                    else
                    {
                        if (st.Count > 0)
                        {
                            key = string.Join("_", st) + "_" + key;
                        }

                        if (ht.ContainsKey(key))
                        {
                            ht[key] += "&" + val;
                        }
                        else
                        {
                            ht.Add(key, val);
                        }
                    }
                }
                else if (line.IndexOf('}') != -1 && st.Count > 0)
                {
                    st.Pop();
                }
            }

            return ht;
        }

        static string ClearString(string str)
        {
            str = str.Trim();

            var ind0 = str.IndexOf("\"");
            var ind1 = str.LastIndexOf("\"");
            
            if (ind0 != -1 && ind1 != -1)
            {
                str = str.Substring(ind0 + 1, ind1 - ind0 - 1);
            }
            else if (str[str.Length-1] == ',')
            {
                str = str.Substring(0, str.Length - 1);
            }

            str = HttpUtility.UrlDecode(str);

            return str;
        }
<source>

Рассмотрим пример json строка полученная от системы Windows LiveId:

<code>
{
   "id": "cb6e111111aaaaaa", 
   "name": "Code Tester", 
   "first_name": "Code", 
   "last_name": "Tester", 
   "link": "http://profile.live.com/cid-cb6e111111aaaaaa/", 
   "gender": "male", 
   "emails": {
      "preferred": "tester@gmail.com", 
      "account": "tester@gmail.com", 
      "personal": null, 
      "business": null
   }, 
   "locale": "en_US", 
   "updated_time": "2012-05-21T21:40:43+0000"
}
</code>

Превращается в словарь:
<code>
id,                 cb6e111111aaaaaa
name,               Code Tester
first_name,         Code
last_name,          Tester
link,               http://profile.live.com/cid-cb6e111111aaaaaa/
gender,             male
emails_preferred,   tester@gmail.com
emails_account,     tester@gmail.com
emails_personal,    null
emails_business,    null
locale,             en_US
updated_time,       2012-05-21T21:40:43+0000
</code>

Есть некоторые ньансы. Как видно из примера вложенные объекты получают префикс родителя. Таким образом мы получили emails_preferred и emails_account. Если же json будет содержать массив объектов, тогда значения будут разделены символом &.

private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            lastPos = e.GetPosition(wnd.LayoutFacility);
        }

        private void Border_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            lastPos = default(Point);
        }

        private void btnSettings_Click(object sender, RoutedEventArgs e)
        {
            var opt = new FacilityOptions(this);

            opt.ShowDialog();
        }

        private void btnRemove_Click(object sender, RoutedEventArgs e)
        {
            wnd.LayoutFacility.Children.Remove(this);
        }

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Data;
  9. using System.Windows.Documents;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Navigation;
  14. using System.Windows.Shapes;
  15.  
  16. namespace WpfTkr
  17. {
  18.     /// <summary>
  19.     /// Interaction logic for Facility.xaml
  20.     /// </summary>
  21.     public partial class Facility : UserControl
  22.     {
  23.         MainWindow wnd;
  24.         Point lastPos;
  25.  
  26.         public Facility()
  27.         {
  28.             InitializeComponent();
  29.         }
  30.  
  31.         public Facility(MainWindow wnd, string name): this()
  32.         {
  33.             this.wnd = wnd;
  34.             this.lastPos = default(Point);
  35.             this.txtName.Text = name;
  36.         }
  37.  
  38.         private void Border_MouseEnter(object sender, MouseEventArgs e)
  39.         {
  40.             var brd = sender as Border;
  41.  
  42.             brd.BorderBrush = new SolidColorBrush(Colors.Red);
  43.  
  44.             LayoutButtons.Visibility = Visibility.Visible;
  45.         }
  46.  
  47.         private void Border_MouseLeave(object sender, MouseEventArgs e)
  48.         {
  49.             var brd = sender as Border;
  50.  
  51.             brd.BorderBrush = new SolidColorBrush(Colors.Black);
  52.             lastPos = default(Point);
  53.  
  54.             LayoutButtons.Visibility = Visibility.Collapsed;
  55.         }
  56.  
  57.         private void Border_MouseMove(object sender, MouseEventArgs e)
  58.         {
  59.             if(wnd.btnMove.IsChecked == true && lastPos != default(Point))
  60.             {
  61.                 var pos = e.GetPosition(wnd.LayoutFacility);
  62.                 var dx = pos.X - lastPos.X;
  63.                 var dy = pos.Y - lastPos.Y;
  64.                 var x = (double)this.GetValue(Canvas.LeftProperty) + dx;
  65.                 var y = (double)this.GetValue(Canvas.TopProperty) + dy;
  66.  
  67.                 this.SetValue(Canvas.LeftProperty, x);
  68.                 this.SetValue(Canvas.TopProperty, y);
  69.  
  70.                 lastPos = pos;
  71.             }
  72.         }
  73.  
  74.         private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  75.         {
  76.             lastPos = e.GetPosition(wnd.LayoutFacility);
  77.         }
  78.  
  79.         private void Border_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
  80.         {
  81.             lastPos = default(Point);
  82.         }
  83.  
  84.         private void btnSettings_Click(object sender, RoutedEventArgs e)
  85.         {
  86.             var opt = new FacilityOptions(this);
  87.  
  88.             opt.ShowDialog();
  89.         }
  90.  
  91.         private void btnRemove_Click(object sender, RoutedEventArgs e)
  92.         {
  93.             wnd.LayoutFacility.Children.Remove(this);
  94.         }
  95.  
  96.     }
  97. }