Audric Ch
Audric Ch

Reputation: 33

Format a string with params from object

I've got in my database a varchar column with data on it like this one :

"Hello my name is {person.firstname} and my house is in {city.name}"

I want to inject my objects in this string to get a full data string fill with data from database.

I'm using .Net 4.7 and it's on a API.

Is that easily possible ? Did I need to use Roselyn Code Analysis ?

Thanks !

Upvotes: 3

Views: 992

Answers (4)

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186803

For given string source

  string source = 
    "Hello my name is {person.firstname} and my house is in {city.name}";

We have to implement 2 steps: first, we should be able for a given name (like person.firstname, city.name) provide a correct response (say, "John" and "New York"). If you have a some kind of context, a collection of instances to query, e.g.

  IEnumerable<object> = new List<object>() {
    new Person() {
      FirstName = "John",
      LastName  = "Smith",
      Sex       = "M",
      Age       = 44  
    },

    new City() {
      Name    = "New York",
      Country = "USA"
    },

    ...  
  };

You can put it like this:

private static string MakeResponse(IEnumerable<object> context, string name) {
  if (string.IsNullOrEmpty(name))
    return "???";
  else if (context == null)
    return "???";

  string[] items = name.Split('.');

  string className = items[0];
  string propertyName = items.Length > 1 ? items[1] : null;

  object data = context
    .Where(item => item != null)
    .FirstOrDefault(item => 
       string.Equals(item.GetType().Name, className, StringComparison.OrdinalIgnoreCase));

  if (null == data)
    return "???";

  if (null == propertyName)
    return data.ToString();

  var property = data
    .GetType()
    .GetProperties(BindingFlags.Public | 
                   BindingFlags.Instance | 
                   BindingFlags.Static)
    .Where(prop => string.Equals(prop.Name, propertyName, StringComparison.OrdinalIgnoreCase))
    .Where(prop => prop.CanRead)
    .Where(prop => !prop.GetIndexParameters().Any())
    .FirstOrDefault();

  if (null == property)
    return "???";

  return property.GetValue(property.GetGetMethod().IsStatic ? null : data)?.ToString() ?? "";
}

Then we have to parse the string, and substitute {...}

private static string ReplaceMyMask(string source, params object[] context) {
  if (null == context)
    return source;

  return Regex.Replace(
    source,
  @"\{\s*[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)*\s*}",
    match => MakeResponse(context, match.Value.Trim(' ', '{', '}'))
  );
}

Demo:

public class Person {
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

public class City {
  public string Name { get; set; }
}

...

var person = new Person() {
  FirstName = "John",
  LastName = "Smith",
},

var city = new City() {
  Name = "New York"
};

...

string stringToModify = 
  "Hello my name is {person.firstname} and my house is in {city.name}"; 

string result = ReplaceMyMask(stringToModify, person, city);

Console.Write(result);

Outcome:

Hello my name is John and my house is in New York

Upvotes: 2

dmigo
dmigo

Reputation: 3029

This approach is quite a lot of code.

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

public class Program
{   
    public static void Main()
    {       
        var person = new{
            firstname = "Jim"
        };
        var city = new {
            name = "Mexico City"
        };
        var text = "Hello my name is {person.firstname} and my house is in {city.name}";

        foreach(var item in Program.ToDictionary(person, "person"))
            text = text.Replace(item.Key, item.Value);

        foreach(var item in Program.ToDictionary(city, "city"))
            text = text.Replace(item.Key, item.Value);

        Console.WriteLine(text);
    }

    private static Dictionary<string, string> ToDictionary(dynamic subj, string prefix){
        var json = JsonConvert.SerializeObject(subj);
        Dictionary<string, string>  deserialized = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
        var result = deserialized.ToDictionary(p => string.Format("{{{0}.{1}}}", prefix, p.Key), p=> p.Value);

        return result;
    }
}

The result will be:

Hello my name is Jim and my house is in Mexico City

Upvotes: 0

rekcul
rekcul

Reputation: 376

I think there are multiple solutions that have advantages and disadvantages for you. So I would just like to show you three ways of how to get data into a certain predefined string.

1. Use the "$" notation

var firstName = "Luke";
var lastName = "Skywalker";

var sentenceWithFullName = $"The Jedi's name is {firstName} {lastName}";

The sentence will be: "The Jedi's name is Luke Skywalker"

This notation comes close to the notation you have in your database but there is a very important difference: The example above is done in code, so is the definition of the sentenceWithFullName. In your case you fetch the "sentence" from your database and the above notation will not work in its way.

2. Use String.Format

var firstName = "Luke";
var lastName = "Skywalker";
var sentenceFromDatabase = "The Jedi's name is {0} {1}";
var sentenceWithFullname = string.Format(sentenceFromDatabase, firstName, lastName);

The sentence will be: "The Jedi's name is Luke Skywalker"

How does it work? The string.Format() replaces the {x} with the given parameters. The first parameter is the string that you want to format, the rest of the parameters are the values you want to be part of the formated string. But be careful: If you want to replace for example five values, you need placeholder from {0}, {1}, {2}, {3}, {4} and give five additional parameters to the string.Format function. Just like this:

var sentenceWithFullname = string.Format("The Jedi's name is {0} {1} and his favorites numbers are: {2}, {3}, {4}", firstName, lastName, "0815", "1337", "4711");

If the number of placeholders and the number of parameters do not fit it can end up in an exception.

3. Use string.Replace()

var firstName = "Luke";
var lastName = "Skywalker";
var sentenceWithFullName = "The Jedi's name is {firstName} {lastName}";
sentenceWithFullName  = sentenceWithFullName.Replace("{firstName}", firstName);
sentenceWithFullName  = sentenceWithFullName.Replace("{lastName}", lastName);

The sentence will be: "The Jedi's name is Luke Skywalker"

This approach is straightforward. You take the string from database and replace the placeholders with the corresponding values coming from properties or variables.

My suggestion

So if we now try to fit those solutions to your problem, I would not go for solution one, as your predefined string comes from a database and is not defined in your code. So the above example will not work for you.

If you have a chance to change the values in the database I would suggest that you go for solution number two. So define the string values with {0} to {n} placeholders and use the String.Format() function on it. If the database string format is fix, this will also not work for you.

In my opinion I do not really like the string.Replace() function, but for your case it could solve your issue. Of course it contains a lot of magic strings and is very vulnerable to any change on database site.

Upvotes: 1

sommmen
sommmen

Reputation: 7648

You can use string.format:

String format msdn

Or you can concat the string together yourself:

var name = "Rick"; // Fill this with the data from the database
var location = "Zwolle, The Netherlands"    
var dataBaseItem = "Hello my name is " + name +  " and my house is in " + location

Or you can use string interpolation, my favorite.

String interpolation msdn

    var name = "Rick";
    var location = "Zwolle, The Netherlands";

    var person = new
    {
        name,
        location
    };

    var databseItem = $"Hello my name is {person.name} and my house is in {person.location}";

Upvotes: 0

Related Questions