33

I am researching about how to load the startup config in .NET Core. I notice there are different ways to do that, I have seen XML, JSON, init file, and from a Dictionary located in memory (I will go back to this later). I'm using something like the code below:

new ConfigurationBuilder().AddJsonFile("file.json").Build();

All of that is OK, but, isn't there any way to load that configuration from a JSON string? I mean, I don't want to store the json in a temporal file because is a real-time built file and It makes no sense.

About the dictionary located in memory. Its easy to build it manually, but, What about for complex and too hierarchical JSON structures? As far as I know, the dictionary is

dictionary < string, string >

whose key is the parents of the tree concatenated by ":" managing at the same time the repeated nodes, enumerating them, etc. A pain build this algorithm from scratch.

TylerH
  • 20,799
  • 66
  • 75
  • 101
mra
  • 349
  • 1
  • 3
  • 5
  • You can read JSON from string as well. Check this link https://stackoverflow.com/a/29843542/2298362 – Prabodh M Jun 28 '17 at 16:33
  • @PrabodhM Thanks for your comment, but It is not related to my question. What I'm questioning is: "Is there any way to load a ConfigurationBuilder() from a json in memory?" – mra Jun 28 '17 at 17:29
  • Sorry I guess I misinterpreted your question. – Prabodh M Jun 28 '17 at 17:36

5 Answers5

46

Use NuGet package Microsoft.Extensions.Configuration.Json >= 3.0.0

var json = "{ \"option1\": 1, \"option2\": \"abc\", }";
var configuration = new ConfigurationBuilder().AddJsonStream(new MemoryStream(Encoding.ASCII.GetBytes(json))).Build();
Pang
  • 9,564
  • 146
  • 81
  • 122
FatTiger
  • 647
  • 5
  • 14
  • 5
    Possibly this solution wasn't available when the original question was asked, but certainly seems like the easiest way to build a configuration directly from a JSON string. Perfect for unit testing! – Ben Wesson Mar 12 '20 at 12:17
  • 1
    Beware there are issues using AddJsonStream when multiple .Build()'s are run. Just ran into this issue cause the first .Build() on a IConfigurationBuilder will destroy the initial streams. Bug report here: https://github.com/dotnet/extensions/issues/3645 – Matt Oct 21 '20 at 17:58
17

I liked Adam's answer a lot, but the interface implementations he linked to were a bit monolithic. Here is a smaller one:

public class InMemoryFileProvider : IFileProvider
{
    private class InMemoryFile : IFileInfo
    {
        private readonly byte[] _data;
        public InMemoryFile(string json) => _data = Encoding.UTF8.GetBytes(json);
        public Stream CreateReadStream() => new MemoryStream(_data);
        public bool Exists { get; } = true;
        public long Length => _data.Length;
        public string PhysicalPath { get; } = string.Empty;
        public string Name { get; } = string.Empty;
        public DateTimeOffset LastModified { get; } = DateTimeOffset.UtcNow;
        public bool IsDirectory { get; } = false;
    }

    private readonly IFileInfo _fileInfo;
    public InMemoryFileProvider(string json) => _fileInfo = new InMemoryFile(json);
    public IFileInfo GetFileInfo(string _) => _fileInfo;
    public IDirectoryContents GetDirectoryContents(string _) => null;
    public IChangeToken Watch(string _) => NullChangeToken.Singleton;
}

Then, as per Adam's answer, you can use:

var memoryFileProvider = new InMemoryFileProvider(jsonString);
var configuration = new ConfigurationBuilder()
    .AddJsonFile(memoryFileProvider, "appsettings.json", false, false)
    .Build();
Rob Lyndon
  • 12,089
  • 5
  • 49
  • 74
7

In ASPNETCORE 2.0 (not sure about other versions) you can use config.AddInMemoryCollection:

var host = new WebHostBuilder()
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        config.AddInMemoryCollection(new Dictionary<string, string>()
        {
            { "MyParentKey:MySubKey", "MyValue" }
        });
    });

UPDATE: I've adapted some code from the link below to parse a JSON string and return a Dictionary:

https://github.com/aspnet/Configuration/blob/d469707ab18eef7ed0002f00175a9ad5b0f36250/src/Config.Json/JsonConfigurationFileParser.cs

using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

namespace Config
{
    public class JsonConfigurationParser
    {
        private JsonConfigurationParser() { }

        private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        private readonly Stack<string> _context = new Stack<string>();
        private string _currentPath;

        public static IDictionary<string, string> Parse(string json) => new JsonConfigurationParser().ParseJson(json);

        private IDictionary<string, string> ParseJson(string json)
        {
            _data.Clear();

            var jsonConfig = JObject.Parse(json);

            VisitJObject(jsonConfig);

            return _data;
        }

        private void VisitJObject(JObject jObject)
        {
            foreach (var property in jObject.Properties())
            {
                EnterContext(property.Name);
                VisitProperty(property);
                ExitContext();
            }
        }

        private void VisitProperty(JProperty property)
        {
            VisitToken(property.Value);
        }

        private void VisitToken(JToken token)
        {
            switch (token.Type)
            {
                case JTokenType.Object:
                    VisitJObject(token.Value<JObject>());
                    break;

                case JTokenType.Array:
                    VisitArray(token.Value<JArray>());
                    break;

                case JTokenType.Integer:
                case JTokenType.Float:
                case JTokenType.String:
                case JTokenType.Boolean:
                case JTokenType.Bytes:
                case JTokenType.Raw:
                case JTokenType.Null:
                    VisitPrimitive(token.Value<JValue>());
                    break;

                default:
                    throw new FormatException("Unsupported JSON token");
            }
        }

        private void VisitArray(JArray array)
        {
            for (int index = 0; index < array.Count; index++)
            {
                EnterContext(index.ToString());
                VisitToken(array[index]);
                ExitContext();
            }
        }

        private void VisitPrimitive(JValue data)
        {
            var key = _currentPath;

            if (_data.ContainsKey(key))
            {
                throw new FormatException("Duplicate Key");
            }
            _data[key] = data.ToString(CultureInfo.InvariantCulture);
        }

        private void EnterContext(string context)
        {
            _context.Push(context);
            _currentPath = ConfigurationPath.Combine(_context.Reverse());
        }

        private void ExitContext()
        {
            _context.Pop();
            _currentPath = ConfigurationPath.Combine(_context.Reverse());
        }
    }
}

USAGE:

var dictionary = JsonConfigurationParser.Parse(MyJsonString);
Francesco D.M.
  • 2,129
  • 20
  • 27
Lee Gunn
  • 8,417
  • 4
  • 38
  • 33
  • 1
    Answer helps, but is still not complete. How would you model a complex json object, this would be tedious as a dictionary. We're looking for some way just to load a JSON string directly. – JohnKoz Aug 10 '18 at 23:12
  • 1
    @JohnKoz - There's some MS code in Github that already converts JSON to the correct dictionary format (it's an internal class). I've adapted it and added it to the answer. Hopefully that helps. – Lee Gunn Aug 11 '18 at 09:06
  • Thank you, your initial reply is very nice. Works very well for unit testing on dotnetcore 2 – Jeremy Ray Brown Oct 14 '20 at 00:04
7

You can re-use the existing API (Microsoft.Extensions.Configuration.Json) relatively easily by implementing an in-memory file provider.

You'll need

  • a dummy IFileProvider implementation - something like the one used here.
  • a dummy IFileInfo implementation - you can borrow one from here.

The code below demonstrates how to assemble all this:

var json = "{ \"option1\": 1, \"option2\": \"abc\", }";
var memoryJsonFile = new MemoryFileInfo("config.json", Encoding.UTF8.GetBytes(json), DateTimeOffset.Now);
var memoryFileProvider = new MockFileProvider(memoryJsonFile);

var configuration = new ConfigurationBuilder()
    .AddJsonFile(memoryFileProvider, "config.json", false, false)
    .Build();

Console.WriteLine(configuration["option2"]);

And there you go ;)

Adam Simon
  • 2,762
  • 16
  • 22
0

You can use .AddJsonStream

var jsonString =  "{...}"    
var configuration = new ConfigurationBuilder()
                           .AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
                           .Build();

you will need

<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
adborroto
  • 9
  • 2