22 January 2012

JSON deserialization with JSON.net: basics

I’ve been contemplating an article about handling JSON for some time now, but it turned out to be a rather long article. So I’d thought to try something new, and write a short series in three parts.

  • Part 1 handles the basics
  • Part 2 handles advanced deserialization with class hierarchies
  • Part 3 handles a caching-and-updating scenarios.

And this is part 1 ;-)

This whole article actually boils down to one line of code, but I need to go trough some hooplah to show you how to use it. It all begins with the data. Consider this piece of quite readable JSON, describing a few recent Windows Phone models.

[
  {
    "Brand": "Nokia","Type" : "Lumia 800",
    "Specs":{"Storage" : "16GB", "Memory": "512MB","Screensize" : "3.7"}
  },
  {
    "Brand": "Nokia", "Type" : "Lumia 710",
    "Specs":{"Storage" : "8GB","Memory": "512MB","Screensize" : "3.7"}
  },  
  { "Brand": "Nokia","Type" : "Lumia 900",
    "Specs":{"Storage" : "8GB", "Memory": "512MB","Screensize" : "4.3" }
  },
  { "Brand": "HTC ","Type" : "Titan II",
    "Specs":{"Storage" : "16GB", "Memory": "512MB","Screensize" : "4.7" }
  },
  { "Brand": "HTC ","Type" : "Radar",
    "Specs":{"Storage" : "8GB", "Memory": "512MB","Screensize" : "3.8" }
  }
]

JSON is rather compact, which is a great feature when you are developing for mobile devices. It has also a few downsides as far as client programming is concerned:

  • generating client code for it that does all the parsing and calling, as for SOAP, is not a standard feature of Visual Studio,
  • it’s almost impossible to read for an ordinary human being,
  • deciphering it into classes is a lot of work,
  • hand coding a parser for it is not fun.

Which is why you don’t. There are several ways of generating classes from JSON, the simplest way is this website: json2csharp by Jonathan Keith. You copy a JSON result into the upper textbox, hit the “Generate” button and out come your classes:Json2Csharp

There are more sites that do the same, by the way, but this is what I use. Next steps:

  • Fire up Visual Studio
  • Create a new Windows Phone project (for instance JsonDemo)
  • Plonk the classes generated above in the project. Bonus cookies if you split them in separate files and add namespaces to them. Bonus donut if you, like me, think “RootObject” is actually a pretty ugly name for an object - so change it to "Phone".
  • Click Tools/Library Package Manager/Manage NuGet Packages for Solution (you do have the NuGet Package Manager installed, don’t you? If not, stop whatever you are doing now and get it right this instance, you hear me ;)? )
  • Search for JSON.Net
  • Click install. This will add a reference to NewtonSoft.Json.dll to your product.
  • Add references to Microsoft.Phone.Reactive and System.Observable because they are going to be needed in the next step.

To make the result visible, add some XAML to the default content panel in Mainpage.Xaml – just a button and a templated ListBox, no rocket science here:

<StackPanel>
  <Button Name="Load"
      VerticalAlignment="Top"
      Content="Load phones" Click="Load_Click" />
  <ListBox x:Name="PhoneList" Height="532">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="{Binding Brand}" 
                 Margin="0,0,12,0" />
          <TextBlock Text="{Binding Type}"/>
        </StackPanel>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</StackPanel>

Finally, open MainPage.Xaml.cs and add the method Load_Click as displayed below.

using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Reactive;
using Newtonsoft.Json;

namespace JsonDemo
{
  public partial class MainPage : PhoneApplicationPage
  {
    // Constructor
    public MainPage()
    {
      InitializeComponent();
    }

    private void Load_Click(object sender, RoutedEventArgs e)
    {
      var w = new WebClient();
      Observable
        .FromEvent<DownloadStringCompletedEventArgs>(w, "DownloadStringCompleted")
        .Subscribe(r =>
        {
          var deserialized = 
            JsonConvert.DeserializeObject<List<Phone>>(r.EventArgs.Result);
          PhoneList.ItemsSource = deserialized;
        });
      w.DownloadStringAsync(
        new Uri("http://www.schaikweb.net/dotnetbyexample/JSONPhones1.txt"));
    }
  }
}

JsonSerializerAnd there it is, the one line of code that this is all about. Call the DeserializeObject method, template it with the return type you want, and stuff the JSON string in it. Result: a list of objects with their properties filled, even if there are things like nested objects (specs in these case) and arrays in there.

If you run the demo solution you get the result displayed in the image on the right. Keep in mind this code is by no means Windows Phone specific. There are JSON.Net implementations for virtually all frameworks available. So should you feel the need to use this from Silverlight or full .NET: it’s there.

You should, by the way, pay attention to the structure of the JSON. The code I show works for a list. A list in JSON starts with a square bracket: [. If your JSON starts with a curly brace: { then you get returned a single object - a so called root object. In that case, your deserialization should code return a single object in stead of a list as well, i.e. something like

var deserialized = 
     JsonConvert.DeserializeObject<Phone>(r.EventArgs.Result);

Finally, a ninja tip:

  • Click Tools/Library Package Manager/Manage NuGet Packages for Solution  again
  • Search for SharpGIS.GZipWebClient
  • Click install
  • Change “WebClient” in the Load_Click method to SharpGIS.GZipWebClient

This plug-in replacement for WebClient by Morten Nielsen adds support for GZIP compressed web requests – this reduces network traffic even further, apparently boosting load performance significantly. You won’t really notice the difference on such a small data files as used in this sample, but as your JSON return values get larger, so will be the impact of using this library.

For the record: I am not ill nor do I have forsaken MVVM, but I tried to make the example as simple as possible so yes, I used a little code behind, as to not to cloud the solution in architectural frills. ;-)

Thanks to Matthijs Hoekstra for putting me on track to this, and to fellow #wp7nl developer Leon Zandman for correcting some annoying typos.

51 comments:

  1. Is the Phone class the same as the RootObject class? The highlighted line of code uses the Phone class, but you declared it as RootObject earlier.

    ReplyDelete
  2. @Sly: have you seen this text in the article?

    Plonk the classes generated above in the project. Bonus cookies if you split them in separate files and add namespaces to them. Bonus donut if you, like me, think “RootObject” is actually a pretty ugly name for an object and change it to Phone.

    ReplyDelete
  3. Thanks, that was a neat write up !

    ReplyDelete
  4. Hi, I just learn how to parse Json in wp. could you give me an example how to show "Storage", "Memory", "Screensize" from Specs class?

    thank you very much

    ReplyDelete
  5. @Muhammad, I am not really sure what you mean. You mean show the other fields? If so, you just add a TextBlock to the StackPanel like this, for instance:
    <TextBlock Text="{Binding Specs.ScreenSize}"/>

    ReplyDelete
  6. Hi,nice tutorial.

    Is it possible to store the unparsed response from the url in a string?

    ReplyDelete
  7. @Narayana thank you I do my best ;-). What you get back from WebClient end up in r.EventArgs.Result - that's a string, and you can store that in any other string you like.

    ReplyDelete
  8. Nice tutorial, but wat if i av a nested json like this:
    {"posts":[{"post":{"id":"2","Hosiptal":"Christ Medical Centre","Address":"28, Randle Road, Apapa, Lagos ","Tag":"Apapa","phone":"1-5870926"}},{"post":{"id":"8","Hosiptal":"Macey Medical Centre ","Address":"Capricorn Block Ground Floor, Eleganza Plaza, Apapa, Lago","Tag":"Apapa","phone":""}}]}
    iv created 3 classes with json2c# online tool. How do access each variable.

    ReplyDelete
  9. @femi well you get 3 objects: RootObject Post and Post2. RootObject contains a List, Post a single Post2 object. This as apparently because of the data structure. What is your problem exactly?

    ReplyDelete
  10. {
    "status": "ok",
    "count": 3,
    "categories": [
    {
    "id": 41,
    "slug": "love-messages",
    "title": "Love Messages",
    "description": "If you have a girlfriend, boyfriend, wife, husband...a secret lover. Then these cute love messages and love quotes are for you. Romantic text messages galore find the perfect message to send to your lovers Mobile Phone.",
    "parent": 0,
    "post_count": 994
    },
    {
    "id": 63,
    "slug": "rajnikant-jokes",
    "title": "Rajnikant Jokes",
    "description": "Are you a Fan of Rajnikant? Here is the collection of some cool messages for Rajnikant. Share and have fun",
    "parent": 0,
    "post_count": 149
    },
    {
    "id": 52,
    "slug": "santa-banta-jokes",
    "title": "Santa Banta Jokes",
    "description": "Do you love Jokes? So, here is the best collection of Santa Banta jokes for you. Send awesome and funny SMS's to your friends!",
    "parent": 0,
    "post_count": 503
    }
    ]
    }


    this is my json and i got the error in class file and near deserialization variable. can you solve this?

    ReplyDelete
  11. @Anup:

    You give very little to work on, so I work on a hunch. I guess you use
    var deserialized = JsonConvert.DeserializeObject<List<YourObject>>(r.EventArgs.Result);

    But your JSON suggest a SINGLE root object. So I'd suggest you try:
    var deserialized = JsonConvert.DeserializeObject<YourObject>(r.EventArgs.Result);

    Does that help?

    ReplyDelete
  12. nop didn't work.....my class is


    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Collections.Generic;


    namespace PNF
    {
    public class Category
    {
    public int id { get; set; }
    public string slug { get; set; }
    public string title { get; set; }
    public string description { get; set; }
    public int parent { get; set; }
    public int post_count { get; set; }
    }

    public class RootObject
    {
    public string status { get; set; }
    public int count { get; set; }
    public List categories { get; set; }
    }
    }

    tell me exact code line and i have to display "title" and json url is http://www.mysmsbasket.com/wp/?json=get_category_index&dev=1

    ReplyDelete
  13. Anup, if I add this classes generated by json2csharp to my demo project and change the code to this:

    private void Load_Click(object sender, RoutedEventArgs e)
    {
    var w = new SharpGIS.GZipWebClient();
    Observable.FromEvent<DownloadStringCompletedEventArgs>(w, "DownloadStringCompleted")
    .Subscribe(r =>
    {
    var deserialized = JsonConvert.DeserializeObject<RootObject>(r.EventArgs.Result);
    MessageBox.Show(deserialized.categories[0].title);
    //PhoneList.ItemsSource = deserialized;
    });
    w.DownloadStringAsync(new Uri("http://www.mysmsbasket.com/wp/?json=get_category_index&dev=1"));
    }

    I get the popup "Love Messages" which is the title of the first message. You should really be able to work out things from here now.

    ReplyDelete
  14. Hi,

    First of I would like to thank you for your example.

    Everything worked correctly for me but I want to store the data gathered from the google URL.

    Here is my detailed problem description:

    http://stackoverflow.com/questions/11978239/jsonconvert-can-not-assign-string-value-to-a-string-variable

    I will be very happy if you could help me.

    Best Regards

    ReplyDelete
  15. Thanks for your interest in my problem Joost. currently i am not in my own pc but i will try this again and will come to you. and thanks again.

    ReplyDelete
  16. @a.t.aydemir, I looked at your problem at Stackoverflow. I posted a possible answer, assuming that I guessed right what you are doing wrong. A hint: next time provide a little more context, like how you are calling you method ;-)

    ReplyDelete
  17. Hi Joost, its working for me now. great work. and need one more help. actually when i click on any list of "title" then in another page i want "content" of that "title". can you help me please??

    ReplyDelete
  18. @Anup that goes way beyond the scope of this article ;-). I suggest you look up navigation and how to pass data between pages. It really isn't that hard

    ReplyDelete
  19. Can you suggest me some links for this type of navigation tutorial???

    ReplyDelete
  20. @anup sure: http://www.silverlightshow.net/items/Windows-Phone-7-Part-3-Understanding-navigation.aspx

    ReplyDelete
  21. I have a JSON result of a list of strings. Is there a way to get the list of strings into a ListBox using a ListBox.ItemTemplate still?

    I can get the results into the listbox, by jut creating a list from the deserialized var, but there's very little customisation of the listbox without the item template (such as word wrapping etc).

    ReplyDelete
  22. @J: Maybe you can try something with the tag in the binding? (e.g. StringFormat=\{0:0.00\} ). And it the end you can always use a converter of course.

    ReplyDelete
  23. I want to serialize a custom search in google like this:
    I do not understand how deo modify your code ... can you help me?

    public class Url
    {
    public string type { get; set; }
    public string template { get; set; }
    }

    public class NextPage
    {
    public string title { get; set; }
    public string totalResults { get; set; }
    public string searchTerms { get; set; }
    public int count { get; set; }
    public int startIndex { get; set; }
    public string inputEncoding { get; set; }
    public string outputEncoding { get; set; }
    public string safe { get; set; }
    public string cx { get; set; }
    }

    public class Request
    {
    public string title { get; set; }
    public string totalResults { get; set; }
    public string searchTerms { get; set; }
    public int count { get; set; }
    public int startIndex { get; set; }
    public string inputEncoding { get; set; }
    public string outputEncoding { get; set; }
    public string safe { get; set; }
    public string cx { get; set; }
    }

    public class Queries
    {
    public List nextPage { get; set; }
    public List request { get; set; }
    }

    public class Context
    {
    public string title { get; set; }
    public List> facets { get; set; }
    }

    public class SearchInformation
    {
    public double searchTime { get; set; }
    public string formattedSearchTime { get; set; }
    public string totalResults { get; set; }
    public string formattedTotalResults { get; set; }
    }

    public class Metatag
    {
    public string progid { get; set; }
    public string originator { get; set; }
    }

    public class CseImage
    {
    public string src { get; set; }
    }

    public class CseThumbnail
    {
    public string width { get; set; }
    public string height { get; set; }
    public string src { get; set; }
    }

    public class Pagemap
    {
    public List metatags { get; set; }
    public List cse_image { get; set; }
    public List cse_thumbnail { get; set; }
    }

    public class Item
    {
    public string kind { get; set; }
    public string title { get; set; }
    public string htmlTitle { get; set; }
    public string link { get; set; }
    public string displayLink { get; set; }
    public string snippet { get; set; }
    public string htmlSnippet { get; set; }
    public string cacheId { get; set; }
    public string formattedUrl { get; set; }
    public string htmlFormattedUrl { get; set; }
    public Pagemap pagemap { get; set; }
    }

    public class RootObject
    {
    public string kind { get; set; }
    public Url url { get; set; }
    public Queries queries { get; set; }
    public Context context { get; set; }
    public SearchInformation searchInformation { get; set; }
    public List items { get; set; }
    }

    ReplyDelete
  24. @Elia you have to be a little more specific than that. It's nice you post a whole lot of code, but what am I supposed to do with int?

    ReplyDelete
  25. you're right I'm sorry,
    I want to create a listbox with research title and link
    only this ..
    Thank you for your attention

    ReplyDelete
  26. @Elia no need to be sorry. What URL did you get this code from? What did you paste into the code generator website?

    ReplyDelete
  27. I solved my problem, I created a class that was wrong, thank you for your time.

    ReplyDelete
  28. this code is causing "System.Net.WebException" when I click the load phones button...

    var deserialized = JsonConvert.DeserializeObject>(r.EventArgs.Result);

    What could be the problem??

    ReplyDelete
  29. @Kumar

    Impossible to tell from this snippet, especially since it is invalid code. I suggest you check if the URL exists at all and if you get a list of JSON objects - or a single root object. That is the most common cause for errors.

    ReplyDelete
  30. sandeep-chourasia.blogspot.com

    thanks i m actulaaly looking deserialize

    {
    "multicast_id": 5181701976783654000,
    "success": 0,
    "failure": 1,
    "canonical_ids": 0,
    "results": [
    {
    "error": "MismatchSenderId"
    }
    ]
    }

    ReplyDelete
  31. @sam could you please elaborate? What's your problem?

    ReplyDelete
  32. Hi,

    I found your comments interesting.While i was using Json for compact framework i found that it is not capable of handling missing property declaration

    for e.g below response deserialization fails saying "firstName1" is not declared. how to handle these
    json = "{\"OperatorList\":[{\"operatorId\":\"22tt\",\"firstName1\":null,\"lastName\":\"\"},{\"operatorId\":\"22\",\"firstName\":\"a\",\"lastName\":\"t\\\\\"}]}";

    ReplyDelete
  33. @iceman you will have to provide me some more code (find my e-mail address op on my blog). Did you follow my sample? Did you download my sample code?

    ReplyDelete
  34. hello sir,
    I want to display images which is getting with json response .i do alot search but i cant find anything .how can i do that.My c# class is here:
    public class ProfileImage
    {
    public object Title { get; set; }
    public object DisplayName { get; set; }
    public string Value { get; set; }
    }
    public class OtherImage
    {
    public object Title { get; set; }
    public object DisplayName { get; set; }
    public string Value { get; set; }
    public string ImageType { get; set; }
    }
    public class Images
    {
    public ProfileImage ProfileImage { get; set; }
    public List OtherImages { get; set; }
    }
    public class UserImages
    {
    public Images Images { get; set; }
    public string User { get; set; }
    }
    public class RootObject
    {
    public string ukey { get; set; }
    public string skey { get; set; }
    public UserImages UserImages { get; set; }
    public Profile profile { get; set; }
    public MatchParameters matchParameters { get; set; }
    public Payment payment { get; set; }
    public string MeetDistance { get; set; }
    }
    Please help..thank you in advance...

    ReplyDelete
  35. @Akash kakadiya Please try to walk a mile in my shoes. I have no idea from what URL you have generated this code. I have no idea in what project you are using it. I am not clairvoyant you know ;-)

    What I suggest you do: download a piece of the JSON and hunt for anything starting with http. If I must venture a guess, I think ProfileImage.Value and OtherImage.Value should contain image URL.

    Good luck.

    ReplyDelete
  36. Respected Sir,here i take json from www.json.org/example.html . Sir now if i want to display menuitem in listbox than how it can be possible...
    public class Menuitem
    {
    public string value { get; set; }
    public string onclick { get; set; }
    }

    public class Popup
    {
    public List menuitem { get; set; }
    }

    public class Menu
    {
    public string id { get; set; }
    public string value { get; set; }
    public Popup popup { get; set; }
    }

    public class RootObject
    {
    public Menu menu { get; set; }

    ReplyDelete
  37. @Akash, see solution a little higher http://dotnetbyexample.blogspot.com/2012/01/json-deserialization-with-jsonnet.html?showComment=1345028921161#c4689023687682152667

    ReplyDelete
  38. Its a very nice tutorial..
    I am stuck with a problem. In the json response that I receive an element can come either as json object or json array.. I am not able to handle this problem

    ex:
    the response can be this
    {"posts":[{"id":"2"},{"id":"2"}]}
    or
    {"posts":{"id":"2"}}

    Please help
    I am using DataContractJsonSerializer

    ReplyDelete
  39. @ravi how about making deserialization classes for both occasions? If one fails, try the other.

    ReplyDelete
  40. Dear Friend, I am serializing and everything is working perfectly, I wonder how I sort my json for certain field (eg date field)

    ReplyDelete
  41. @Idéias Mobile I don't quite understand what you mean. Deserialized data with a Date in it can simply be ordered with an .OrderBy() call, but I don't think that's what you mean

    ReplyDelete
  42. @Idéias Mobile I don't quite understand what you mean. Deserialized data with a Date in it can simply be ordered with an .OrderBy() call, but I don't think that's what you mean

    ReplyDelete
  43. Gostaria de ordenar os campos do meu json por data

    Meu exemplo está funcionando mas trazendo os dados da maneira padrão (sem ordenação).

    Não sei por onde começar!

    private void Load_Click(object sender, RoutedEventArgs e)
    {
    var w = new WebClient();
    Observable
    .FromEvent(w, "DownloadStringCompleted")
    .Subscribe(r =>
    {
    var deserialized =
    var deserialized =
    JsonConvert.DeserializeObject(r.EventArgs.Result);
    PhoneList.ItemsSource = deserialized.time.toArray();
    });
    w.DownloadStringAsync(
    new Uri("http://application-ideiasmobile.rhcloud.com/parazao/rest/servico/pegaTimes"));
    }

    ReplyDelete
  44. @Idéias Mobile my Portuguese is very bad (i.e. no existent but fortunately Bing translator helped. But there is no date in your json.

    If you want to order by a field, say, cidade, you can try this;

    PhoneList.ItemsSource = deserialized.time.OrderBy( p=> p.cidade).ToList();

    You will need to add a using "System.Linq" on top of you C# file.

    Does that help?

    ReplyDelete
  45. Hello Sir,

    This is my class

    public class Answer
    {
    public string answerId { get; set; }
    public string answer { get; set; }
    }

    public class Question
    {
    public string questionId { get; set; }
    public string questionTitle { get; set; }
    public string storyUrl { get; set; }
    public string correctAnswerId { get; set; }
    public List answers { get; set; }
    }

    public class RootObject
    {
    public string response { get; set; }
    public string message { get; set; }
    public string questionType { get; set; }
    public string device_id { get; set; }
    public string quiz_type { get; set; }
    public int totalQuestion { get; set; }
    public List questions { get; set; }
    }

    i want my question will be bind on a textblock with their respective option in a radio button , a submit button to check the option select on radio button & a next button to get next question

    I do following steps to achieve that:-

    public partial class Quiz : PhoneApplicationPage
    {
    WebClient wc = new WebClient();

    public static string val;

    private static IEnumerator iterator;
    public Quiz()
    {
    InitializeComponent();
    GetDeviceUniqueID();

    wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
    wc.DownloadStringAsync(new Uri("MY url"));
    }

    public static byte[] GetDeviceUniqueID()
    {
    byte[] result = null;
    object uniqueId;
    if (DeviceExtendedProperties.TryGetValue("DeviceUniqueId", out uniqueId))
    {
    result = (byte[])uniqueId;
    }
    val = Convert.ToBase64String(result);
    return result;
    }
    protected void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
    var rootObject = JsonConvert.DeserializeObject(e.Result);
    List obj = new List();

    rootObject.device_id = val;

    Question ques = new Question
    {
    questionTitle = rootObject.questions.Last().questionTitle,
    answers = rootObject.questions.Last().answers.Select(ans => new Answer { answer = ans.answer, answerId = ans.answerId }).ToList(),
    questionId = rootObject.questions.Last().questionId,
    storyUrl = rootObject.questions.Last().storyUrl,
    correctAnswerId = rootObject.questions.Last().correctAnswerId

    };
    txtQuestion.Text = ques.questionTitle;
    rb1.Content = ques.answers.ElementAt(0).answer;
    rb2.Content = ques.answers.ElementAt(1).answer;
    rb3.Content = ques.answers.ElementAt(2).answer;
    rb4.Content = ques.answers.ElementAt(3).answer;


    }
    I know this will only bind last question.
    my problem is how to get next question on "Next" Button.
    and check answer on "Submit" button.

    please help me on this ..
    I am a beginner in windows phone

    ReplyDelete
  46. @rishiraj how about in stead of using LINQ just get your question data with and index? In stead of rootObject.questions.Last() rootObject.questions.[i]? I would have to see your whole app to understand what you are trying to do, but if you use and index and just do i=i+1 on next, you are halfway there I think

    ReplyDelete
  47. Hello sir, thanks for reply..
    as you see in code that rootobject hold all the properties of RootObject class.Now in this object i bind the data from URL in my textblock & radio button.

    but how do i bind answerid & correctanswerid on a button say "SUBMIT" to check which option user select.

    similarly how do i get next question on "NEXT" button click.

    please help me on this...

    ReplyDelete
  48. @rishiraj My dear friend, you paste a bunch of code on my blog, which is a part of your project, I presume. I have to guess from a few lines description what you are trying to do. I will first have to reconstruct your project, fill in the missing details... can't you put your project on a SkyDrive/OneDrive so I can download and have a look at it?

    ReplyDelete
  49. I'm working on windows Phone 7 and got this error: The type 'System.Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.

    ReplyDelete
  50. @Rayze have you tried downloading the sample and see what is different?

    ReplyDelete

Note: Only a member of this blog may post a comment.