1

I want to bind the configuration from a JSON file to an instance of HttpApiClientOptions. However, I encountered some issues when binding the Body property. After defining it as Dictionary<string, object>?, the Body property correctly contains the model and temperature key-value pairs, but the value of messages is empty, and I'm unable to retrieve {"role":"user", "content":"hello!"} from it.

C# Code:

var config = new ConfigurationBuilder()
    .AddJsonFile(configFilePath, false, false)
    .Build();
//...
config.GetSection("apiClientOptions").GetSection(apiClientName).Bind(apiClientOptions);

HttpApiClientOptions Class:

public class HttpApiClientOptions : BaseApiClientOptions
{
    public string Url { get; set; }

    public Dictionary<string, string>? Headers { get; set; }

    public Dictionary<string, string>? Params { get; set; }

    public Dictionary<string, object>? Body { get; set; }
}

JSON Configuration:

{
    "url": "https://example.com",
    "headers": {
        "Authorization": "Bearer sk-"
    },
    "params": {},
    "body": {"model": "gpt-3.5-turbo", "messages": [{"role":"user", "content":"hello!"}], "temperature": 0.7}
}

I attempted to define Body as JObject or JsonElement, but this approach also failed to retrieve values from the first level of nesting.

The reason I did not map the entire Body node to a specific type is that the contents and nesting depth under the Body node are uncertain. Is there any way to make the program read all the information inside the body node from the JSON file during configuration?

TonWin
  • 21
  • 2
  • Not sure it's a duplicate, but did you read [How can I deserialize JSON to a simple Dictionary in ASP.NET?](https://stackoverflow.com/q/1207731/3094533)? – Zohar Peled Aug 03 '23 at 11:26
  • You should understand one thing, configuration is not JSON, it is collection of key-value pairs, JSON files are not a single source of config. So `Dictionary` does not make much sense to binder. – Guru Stron Aug 03 '23 at 11:38
  • @ZoharPeled The question only discussed the scenario where the JSON has only one level of nesting. What I want to achieve is to read the content of a node containing multiple layers of nesting into a property. – TonWin Aug 03 '23 at 11:46

3 Answers3

0

You should understand one thing, configuration is not JSON, it is collection of key-value pairs, JSON files are not a single source of config. From the docs:

Configuration in .NET is performed using one or more configuration providers. Configuration providers read configuration data from key-value pairs using various configuration sources:

  • Settings files, such as appsettings.json
  • Environment variables
  • Command-line arguments
  • ...

So Dictionary<string, object> does not make much sense to binder. One option - provide actual types for all levels:

public class HttpApiClientOptions 
{
    public string Url { get; set; }
    public Dictionary<string,string> Headers { get; set; }
    public Dictionary<string,string> Params { get; set; }
    public Body Body { get; set; }
}

public class Body
{
    public string Model { get; set; }
    public List<Message> Messages { get; set; } // or List<Dictionary<string,string>>
    public double Temperature { get; set; }
}

public class Message
{
    public string Role { get; set; }
    public string Content { get; set; }
}

Another way would be to exclude Body from the model and dynamically read config via GetChildren from some point and process results:

public class HttpApiClientOptions 
{
    public string Url { get; set; }
    public Dictionary<string,string> Headers { get; set; }
    public Dictionary<string,string> Params { get; set; }
}

And

var body = builder.Configuration.GetSection("Body");
foreach (var section in body.GetChildren())
{
    // recursively process section
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • The content under the `Body` node is uncertain, so I haven't provided an actual type for it. I tried defining 'Body' as other types, but still, I couldn't read the entire content of the 'Body' node into a specific property. The value of `messages` is always missing. – TonWin Aug 03 '23 at 12:02
  • @TonWin there is not much what can be done. You need to provide some valid structure for reasons explicitly stated in answer. Also what is the point of configuration if it essentially unstructured? How do you want to consume it? – Guru Stron Aug 03 '23 at 12:05
  • I want to fill the content of the `body` into the HTTP request's request body, and users can customize the structure of this HTTP request's body to adapt to different APIs of large language models. I will provide some placeholders that can be used in the body, and the program will replace these placeholders with prompts. – TonWin Aug 03 '23 at 12:20
  • @TonWin TBH I would not use settings mechanism for that. Personally I would set up some convention (maybe using settings) so users can just drop files for bodies somewhere and that's it. – Guru Stron Aug 03 '23 at 12:24
0

Now, I've also defined Body as Dictionary<string, string>, where the value contains the original content that was under the Body node. However, this approach becomes very cumbersome when filling out the JSON configuration file. Is there a better way to do this?

Example of the "body" field in the JSON configuration file:

"body": "{\"model\": \"gpt-3.5-turbo\", \"messages\": [{\"role\": \"user\", \"content\": \"hello!\"}], \"temperature\": 0.7}"
TonWin
  • 21
  • 2
0

naitive config deserialiazation works only for one level. If you have a nested objects , better to use this syntax

  var json = File.ReadAllText(configFilePath);

  var result = JObject.Parse(json)["apiClientOptions"]
                      .ToObject<HttpApiClientOptions>();

test

string role = (string) ((JArray) result.Body["messages"])[0]["role"]; // "user"
Serge
  • 40,935
  • 4
  • 18
  • 45