Yijafe
Yijafe

Reputation: 3

JSON.NET Deserializer is returning Null

EDIT: It is not related to the try/catch return null statement. I have debugged it and watched it and ensured it does not go to that catch block. I even commented it out, to no avail.

I am tiring to build a configuration manager for my application. Essentially I have certain variables that need to be stored in a JSON file and read during application startup so that certain values are known.

For some of these config variables, I'll allow the user to override the default, so if the "user" key has a non-empty string, I'll use that. If there is no user key or if there is an empty string, I'll use the default. To satisfy this requirement I wrote "AllowUserConfig" and "CoalesceUserDefault" methods to take care of that.

At this point, I have a JSON file (below). I copied the data and pasted into visual studio as a class, JSONConfig. I then have a ConfigManager file that does the real work and my program.cs calls the ConfigManager to use the DeserializeConfigVariables method to read the config file (which is in the correct location), and create a JSONConfig object from that.

With that being said, the JSONConfig object in DeserializeConfigVariables is being returned as null (on the line that says configVariables = serializer.Deserialize<JSONConfig>(jsonReader);). Is there something I'm missing. I've gone over everything a hundred times and can't see what I'm doing incorrectly.

Any and all help would be appreciated.

This is my JSON:

{
  "format": {
     "date": {
        "default": "yyyyMMdd",
        "user": ""
     },
     "month_year": {
        "default": "MM_YYYY",
        "user": ""
     }
  },
  "placeholders": {
     "current_date": "{date}",
     "month_year": "{month_year}",
     "last_monday": "{prev_monday}",
     "next_monday": "{next_monday}"
  },
  "resource_locations": {
     "directories": {
        "root_working": {
           "default": "C:\\ALL",
           "user": ""
        },
        "exports": {
           "default": "C:\\ALL\\Exports",
           "user": ""
        },
        "completed_exports": {
           "default": "C:\\ALL\\Done",
           "user": ""
        },
        "archived": {
           "default": "C:\\ALL\\Archives",
           "user": ""
        }
     },
     "compression": {
        "filename": {
           "default": "{next_monday}_daily_{date}.zip",
           "user": ""
        }
     },
     "logging": {
        "directory": "logs",
        "process_filename": "activity_log_{month_year}.log",
        "process_error_filename": "errors.log",
        "system_error_filename": "sys_errors.log"
     }
  }
}

And this is the JSONConfig class I made by copying and pasting JSON as class in Visual Studio:

using System;
using Newtonsoft.Json;

namespace Config
{

   public class JSONConfig
   {
       public RootObject ConfigVariables { get; set; }
   }

   public class RootObject
   {
       public Format format { get; set; }
       public Placeholders placeholders { get; set; }
       public Resource_Locations resource_locations { get; set; }
   }

   public class Format
   {
       public Date date { get; set; }
       public Month_Year month_year { get; set; }
   }

   public class Date
   {
       public string _default { get; set; }
       public string user { get; set; }
   }

   public class Month_Year
   {
       public string _default { get; set; }
       public string user { get; set; }
   }

   public class Placeholders
   {
       public string current_date { get; set; }
       public string month_year { get; set; }
       public string last_monday { get; set; }
       public string next_monday { get; set; }
   }

   public class Resource_Locations
   {
       public Directories directories { get; set; }
       public Compression compression { get; set; }
       public Logging logging { get; set; }
   }

   public class Directories
   {
       public Root_Working root_working { get; set; }
       public Exports exports { get; set; }
       public Completed_Exports completed_exports { get; set; }
       public Archived archived { get; set; }
   }

   public class Root_Working
   {
       public string _default { get; set; }
       public string user { get; set; }
   }

   public class Exports
   {
       public string _default { get; set; }
       public string user { get; set; }
   }

   public class Completed_Exports
   {
       public string _default { get; set; }
       public string user { get; set; }
   }

   public class Archived
   {
       public string _default { get; set; }
       public string user { get; set; }
   }


   public class Compression
   {
       public Filename filename { get; set; }
   }

   public class Filename
   {
       public string _default { get; set; }
       public string user { get; set; }
   }


   public class Logging
   {
       public string directory { get; set; }
       public string process_filename { get; set; }
       public string process_error_filename { get; set; }
       public string system_error_filename { get; set; }
   }


}

Then, in my ConfigManager files I have the following:

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Linq;
using Newtonsoft.Json;

namespace Config
{
   static class ConfigManager
   {
       // Assumes parent directory is bin.  Assumes config is sibling to bin.
       private static string _initialConfig = @"config\config.json";
       public static string ConfigPath() => Path.Combine(GetRootAppDir(), _initialConfig);


       public static string Parent(string directory)
       {
           string parentDirectory = Directory.GetParent(directory).FullName;
           return parentDirectory;
       }

       public static string GetRootAppDir()
       {
           return Parent(Parent(Directory.GetCurrentDirectory()));
       }

       public static JSONConfig DeserializeConfigVariables()
       {
           try
           {
               var configVariables = new JSONConfig();
               var serializer = new JsonSerializer();
               Console.WriteLine($"Config Path: {ConfigPath()}"); //testing
               using (var reader = new StreamReader(ConfigPath()))
               using (var jsonReader = new JsonTextReader(reader))
               {
                   configVariables = serializer.Deserialize<JSONConfig>(jsonReader);
               }

               return configVariables;
           }
           catch (System.IO.DirectoryNotFoundException ex)
           {
               Console.WriteLine(ex.Message);
               return null;
           }

       }
       public static bool AllowUserConfig(object configVariable)
       {
           string userFieldName = "user";
           var objType = configVariable.GetType();
           return objType.GetMethod(userFieldName) != null;
       }

       public static string CoalesceUserDefault(dynamic configVariable)
       {
           if (AllowUserConfig(configVariable))
           {
               if (!(String.IsNullOrEmpty(configVariable.user)))
               {
                   return configVariable.user;
               }
           }
           return configVariable._default;
       }

   }
}

Upvotes: 0

Views: 243

Answers (4)

Jawad
Jawad

Reputation: 11364

You need to deserialize your json to RootObject, not JSONConfig

configVariables = serializer.Deserialize<RootObject>(jsonReader);

Your json has three root objects, Format, Placeholders and ResourceLocations... but the class you used to deserialize the json to has only one object.. and doesnt match up.

You can always use www.json2csharp.com, paste your json there and see which object you need to deserialize to (base is always RootObject there).

How to use JSONConfig class

If you really want to use JSONConfig to deserialize your json, then you will need to modify your JSON a bit. You will need to add another root element ConfigVariables to the json.

{ "ConfigVariables" :
 {
   "format": {
      "date": {
         "default": "yyyyMMdd",
         "user": ""
 ...

Recommendation

Change the method that deserializes your JSON to a simpler process as well.

public static RootObject DeserializeConfigVariables()
{
    return JsonConvert.DeserializeObject<RootObject>(File.ReadAllLines(ConfigPath()));
}

Upvotes: 0

Anu Viswan
Anu Viswan

Reputation: 18155

Your Json doesn't contain a property called ConfigVariable at the root as defined in the class JSONConfig (the type to which you are attempting to deserialize).

The Json, if you inspect, suits the definition of RootObject. You should deserialize your class to an instance of RootObject.You could assign it to the ConfigVariable property of instance of JsonConfig if your intention is to store the configuration in instance of JsonConfig.

configVariables.ConfigVariables  = serializer.Deserialize<RootObject>(jsonReader);

Upvotes: 1

Vyacheslav Benedichuk
Vyacheslav Benedichuk

Reputation: 387

Your Json file does not correspond to object structure which you are using to deserealize it. Your JsonConfig file contains ConfigVariables property but json file contains format, placeholders, resource_locations proerties which should be on second level. Try to update your config file following way:

{

  "ConfigVariables": {
    "format": {
      "date": {
        "default": "yyyyMMdd",
        "user": ""
      },
      "month_year": {
        "default": "MM_YYYY",
        "user": ""
      }
    },
    "placeholders": {
      "current_date": "{date}",
      "month_year": "{month_year}",
      "last_monday": "{prev_monday}",
      "next_monday": "{next_monday}"
    },
    "resource_locations": {
      "directories": {
        "root_working": {
          "default": "C:\\ALL",
          "user": ""
        },
        "exports": {
          "default": "C:\\ALL\\Exports",
          "user": ""
        },
        "completed_exports": {
          "default": "C:\\ALL\\Done",
          "user": ""
        },
        "archived": {
          "default": "C:\\ALL\\Archives",
          "user": ""
        }
      },
      "compression": {
        "filename": {
          "default": "{next_monday}_daily_{date}.zip",
          "user": ""
        }
      },
      "logging": {
        "directory": "logs",
        "process_filename": "activity_log_{month_year}.log",
        "process_error_filename": "errors.log",
        "system_error_filename": "sys_errors.log"
      }
    }
  }
}

It will solve your problem. By the way, as I see you've named properties in your configuration objects in json style. E.g. resource_locations it's better to name properties in regular way and add JsonProperty attribute for correct mapping. e.g.

[JsonProperty('resource_locations')]
public ResourceLocations ResouceLocations { get; set; }

Upvotes: 0

Nitika Chopra
Nitika Chopra

Reputation: 1405

Hope, it helps you.

You can add yourjsonfile.json file e.g.:-

yourjsonfile.json

    {
      "key1": "value1",
      "key2": "value2",
     "ConnectionStrings": {
          "$ref": "anotherjsonfile.json#"
       }
    }

anotherjsonfile.json

{
     "key2": "value4"
}

Make sure that yourjsonfile.json and anotherjsonfile.json files property set "Copy To Output Directory" to "Copy always".

Find Values from yourjsonfile.json and anotherjsonfile.json files like this-

var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

    IConfiguration configuration = builder.Build();

    string GetValue1 = configuration.GetSection("key1").Value;
    string GetValue2 = configuration.GetSection("key2").Value;

     var builder1 = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile(configuration.GetConnectionString("$ref").Replace("#", ""), optional: true, reloadOnChange: true);
    IConfiguration configuration1 = builder1.Build();

    GetValue2 = (configuration1.GetSection("key2").Value) != null ? configuration1.GetSection("key2").Value : GetValue2;

Thanks!!!

Upvotes: 0

Related Questions