1

C#

Let's say that I want to make a winform page that receives a certain amount of random features read from the contents of a json file. (while using the 'Newtonsoft.Json' library)

Code that Deserializes the json file

 Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(File.ReadAllText(@"..\..\Json\features.json"));

These classes are set to represent the contents and structure of the json file within the same c# file, but outside of the main class of course.

class Root
{
    public List<Category> category { get; set; }
}

class Category
{
    public string name { get; set; }
    public List<Feature> feature { get; set; }
}

class Feature
{
    public string name { get; set; }
    public string frequency { get; set; }
}

I have followed the practices to make this happen here:

It works, however there are two main problems I can see with this:

  1. It can end up visually cluttering the .cs file with lots of classes that you'll never need to look or edit, especially when it is interpreting multiple json files
  2. It isn't dynamic, and it will only work for one json file that you have to make compatible by creating these classes for every subclass that exists within the json file.

My question: Is there a way to deserialize the json file without having to resort to creating multiple classes for every sub-category of data in the json string?

tymtam
  • 31,798
  • 8
  • 86
  • 126
Joeygr
  • 57
  • 5
  • 1. Put them in a separate file 2. Use `dynamic` if you *really* have to (avoid that if you don't, as proper classes are much better). Make use of `Dictionary` for dynamic keys – Charlieface Jun 19 '22 at 21:53
  • @Charlieface That's one way to solve the problem, but then that will also clutter my file structure. Is there a way to put it in a folder directory, something like "src_json" and somehow call the classes while outside of the directory? "using ClassName 'src_json/ClassName' " maybe? – Joeygr Jun 19 '22 at 21:57
  • It can end up ... with lots of classes. How do we know what do you mean? What did you post seems quite generic. Can you pls post a couple of you json to understand your problem. – Serge Jun 19 '22 at 22:06
  • @Serge what I mean by that simply is that I have to create classes in relation to the json data that I have, and if the json file is large and has multiple sub-categories, then it ends up with me having to make multiple classes, and my question was to ask if there was a way to not have to make these classes. Like a way to just get the root contents of the json data without making classes for the specific json content. – Joeygr Jun 19 '22 at 22:11
  • @Joeygr You can get data from json without creating any classes at all, or if you need only part of json data you can create classes for this part only. This is why you have to post a couple of jsons and show what data you need and I can advise how to get it. – Serge Jun 19 '22 at 22:15
  • Visual Studio can do "Paste JSON as Class" which will create the classes for you. You can dump them all in a single file, or put them into a folder in your project. You can put a separate namespace on them if you don't want to clutter your main namespace. – Charlieface Jun 20 '22 at 08:17

2 Answers2

1

I believe you don't need the classes, because you don't need the whole JSON string, is that correct?

If so, instead of deserializing the whole json file, you could partially deserialize only the parts which you are interested in.

Have a look at this example from the Newtonsoft.Json documentation, where we have a long json string representing a response from a Google search, but are only interested in the responseData/results part of it, and only in some fields of that result object:

Object to partially desierialize:

public class SearchResult
{
    public string Title { get; set; }
    public string Content { get; set; }
    public string Url { get; set; }
}

Deserializing Partial JSON Fragment Example:

string googleSearchText = @"{
  'responseData': {
    'results': [
      {
        'GsearchResultClass': 'GwebSearch',
        'unescapedUrl': 'http://en.wikipedia.org/wiki/Paris_Hilton',
        'url': 'http://en.wikipedia.org/wiki/Paris_Hilton',
        'visibleUrl': 'en.wikipedia.org',
        'cacheUrl': 'http://www.google.com/search?q=cache:TwrPfhd22hYJ:en.wikipedia.org',
        'title': '<b>Paris Hilton</b> - Wikipedia, the free encyclopedia',
        'titleNoFormatting': 'Paris Hilton - Wikipedia, the free encyclopedia',
        'content': '[1] In 2006, she released her debut album...'
      },
      {
        'GsearchResultClass': 'GwebSearch',
        'unescapedUrl': 'http://www.imdb.com/name/nm0385296/',
        'url': 'http://www.imdb.com/name/nm0385296/',
        'visibleUrl': 'www.imdb.com',
        'cacheUrl': 'http://www.google.com/search?q=cache:1i34KkqnsooJ:www.imdb.com',
        'title': '<b>Paris Hilton</b>',
        'titleNoFormatting': 'Paris Hilton',
        'content': 'Self: Zoolander. Socialite <b>Paris Hilton</b>...'
      }
    ],
    'cursor': {
      'pages': [
        {
          'start': '0',
          'label': 1
        },
        {
          'start': '4',
          'label': 2
        },
        {
          'start': '8',
          'label': 3
        },
        {
          'start': '12',
          'label': 4
        }
      ],
      'estimatedResultCount': '59600000',
      'currentPageIndex': 0,
      'moreResultsUrl': 'http://www.google.com/search?oe=utf8&ie=utf8...'
    }
  },
  'responseDetails': null,
  'responseStatus': 200
}";

// Parse JSON into a JObject, which we can easily traverse
JObject googleSearch = JObject.Parse(googleSearchText);

// get JSON result objects into a list
IList<JToken> results = googleSearch["responseData"]["results"].Children().ToList();

// serialize JSON results into .NET objects
IList<SearchResult> searchResults = new List<SearchResult>();
foreach (JToken result in results)
{
    // JToken.ToObject is a helper method that uses JsonSerializer internally
    SearchResult searchResult = result.ToObject<SearchResult>();
    searchResults.Add(searchResult);
}

// Title = <b>Paris Hilton</b> - Wikipedia, the free encyclopedia
// Content = [1] In 2006, she released her debut album...
// Url = http://en.wikipedia.org/wiki/Paris_Hilton

// Title = <b>Paris Hilton</b>
// Content = Self: Zoolander. Socialite <b>Paris Hilton</b>...
// Url = http://www.imdb.com/name/nm0385296/

This way, we only need to create a class for SearchResult and for nothing else, which sounds like what you want to have. While traversing the JSON object with code like googleSearch["responseData"]["results"] you can check whether the result is null and act accordingly, which means you can have optional fields in your JSON file, which are not present in other files, without your code breaking.

Does this help you solve your issues?

DomJ
  • 51
  • 6
1

I believe that best practice is using classes.

Here are my arguments for using classes in your case:

  1. You can read json bit by bit, use JObject.Parse or even dynamic (ugh) but it think your code should depend on a class (a dto) not on a json string. This input structure happens to be stored in json, but may not be. Most of your unit tests should take in an object, not a string.
  2. I find the argument of deserialising to classes not being dynamic weak because you need to write the code that will handle the added elements. In other words if you add a new feature to json it won't just work, you need to write the code to support it and each time you change the json structure you need to update your code anyway.
tymtam
  • 31,798
  • 8
  • 86
  • 126