tags:

views:

193

answers:

8

So if I have a method of parsing a text file and returning a list of a list of key value pairs, and want to create objects from the kvps returned (each list of kvps represents a different object), what would be the best method?

The first method that pops into mind is pretty simple, just keep a list of keywords:

private const string NAME   = "name";
private const string PREFIX = "prefix";

and check against the keys I get for the constants I want, defined above. This is a fairly core piece of the project I'm working on though, so I want to do it well; does anyone have any more robust suggestions (not saying there's anything inherently un-robust about the above method - I'm just asking around)?

Edit:

More details have been asked for. I'm working on a little game in my spare time, and I am building up the game world with configuration files. There are four - one defines all creatures, another defines all areas (and their locations in a map), another all objects, and a final one defines various configuration options and things that don't fit else where. With the first three configuration files, I will be creating objects based on the content of the files - it will be quite text-heavy, so there will be a lot of strings, things like names, plurals, prefixes - that sort of thing. The configuration values are all like so:

-
key: value 
key: value
-
key: value
key: value
-

Where the '-' line denotes a new section/object.

+1  A: 

You could create an interface that matched the column names, and then use the Reflection.Emit API to create a type at runtime that gave access to the data in the fields.

Eric Z Beard
+2  A: 

Take a deep look at the XmlSerializer. Even if you are constrained to not use XML on-disk, you might want to copy some of its features. This could then look like this:

public class DataObject {
  [Column("name")]
  public string Name { get; set; }

  [Column("prefix")]
  public string Prefix { get; set; }
}

Be careful though to include some kind of format version in your files, or you will be in hell's kitchen come the next format change.

David Schmitt
+2  A: 

What do you need object for? The way you describe it, you'll use them as some kind (of key-wise) restricted map anyway. If you do not need some kind of inheritance, I'd simply wrap a map-like structure into a object like this:

[java-inspired pseudo-code:]
class RestrictedKVDataStore {
   const ALLOWED_KEYS = new Collection('name', 'prefix');
   Map data = new Map();

   void put(String key, Object value) {
      if (ALLOWED_KEYS.contains(key))
          data.put(key, value)
   }

   Object get(String key) {
      return data.get(key);
   }
}
Argelbargel
A: 

@David:
I already have the parser (and most of these will be hand written, so I decided against XML). But that looks like I really nice way of doing it; I'll have to check it out. Excellent point about versioning too.

@Argelbargel:
That looks good too. :')

Bernard
+2  A: 

Making a lot of unwarranted assumptions, I think that the best approach would be to create a Factory that will receive the list of key value pairs and return the proper object or throw an exception if it's invalid (or create a dummy object, or whatever is better in the particular case).

private class Factory {

   public static IConfigurationObject Factory(List<string> keyValuePair) {

       switch (keyValuePair[0]) {

          case "x":
              return new x(keyValuePair[1]);
              break;
          /* etc. */
          default:
              throw new ArgumentException("Wrong parameter in the file");
       }

  }

}

The strongest assumption here is that all your objects can be treated partly like the same (ie, they implement the same interface (IConfigurationObject in the example) or belong to the same inheritance tree).

If they don't, then it depends on your program flow and what are you doing with them. But nonetheless, they should :)

EDIT: Given your explanation, you could have one Factory per file type, the switch in it would be the authoritative source on the allowed types per file type and they probably share something in common. Reflection is possible, but it's riskier because it's less obvious and self documenting than this one.

Vinko Vrsalovic
A: 

Hi Bernard,

...This is a fairly core piece of the project I'm working on though...

Is it really?

It's tempting to just abstract it and provide a basic implementation with the intention of refactoring later on.

Then you can get on with what matters: the game.

Just a thought

<bb />

A: 

Is it really?

Yes; I have thought this out. Far be it from me to do more work than neccessary. :')

Bernard
+1  A: 

EDIT:

Scratch that, this still applies, but I think what your doing is reading a configuration file and parsing it into this:

List<List<KeyValuePair<String,String>>> itemConfig = 
    new List<List<KeyValuePair<String,String>>>();

In this case, we can still use a reflection factory to instantiate the objects, I'd just pass in the nested inner list to it, instead of passing each individual key/value pair.

OLD POST:

Here is a clever little way to do this using reflection:

The basic idea:

  • Use a common base class for each Object class.
  • Put all of these classes in their own assembly.
  • Put this factory in that assembly too.
  • Pass in the KeyValuePair that you read from your config, and in return it finds the class that matches KV.Key and instantiates it with KV.Value
   
      public class KeyValueToObjectFactory
      { 
         private Dictionary _kvTypes = new Dictionary();

        public KeyValueToObjectFactory()
        {
            // Preload the Types into a dictionary so we can look them up later
            // Obviously, you want to reuse the factory to minimize overhead, so don't
            // do something stupid like instantiate a new factory in a loop.

            foreach (Type type in typeof(KeyValueToObjectFactory).Assembly.GetTypes())
            {
                if (type.IsSubclassOf(typeof(KVObjectBase)))
                {
                    _kvTypes[type.Name.ToLower()] = type;
                }
            }
        }

        public KVObjectBase CreateObjectFromKV(KeyValuePair kv)
        {
            if (kv != null)
            {
                string kvName = kv.Key;

                // If the Type information is in our Dictionary, instantiate a new instance of that class.
                Type kvType;
                if (_kvTypes.TryGetValue(kvName, out kvType))
                {
                    return (KVObjectBase)Activator.CreateInstance(kvType, kv.Value);
                }
                else
                {
                    throw new ArgumentException("Unrecognized KV Pair");
                }
            }
            else
            {
                return null;
            }
        }
    }
FlySwat