2

I have a static class with static fields and a json.

I can deserialize the json into a dynamic object, so I have all the fields and they match exactly the static fields in the class.

How can I use reflection to enumerate the fields and copy the values from the dynamic class into the static class fields?

I can't change the architecture, make it a singleton, etc; it's shared code and the class is going to remain static since it's globally shared settings object used by a shared library.

The solution needs to use reflection since the class evolves over time with new members. Otherwise I could have written a custom deserializer.


Adding more details, but there is really not much:

I have this static class:

static class A
{
    static int I;
    static string S;
}

and a json matching the fields exactly:

{
    "I" : 3,
    "S" : "hello"
}

var Data = JsonConvert.Deserialize<dynamic>(...);

I would like to initialize the static fields of class A with the values I deserialized from the json, into a dynamic object.


Another edit:

I came up with something similar to what David wrote, but this is less efficient since I use the deserializer to convert types, so David's solution is better.

here's what I came up with:

foreach (var Destination in typeof(Settings).GetProperties())
{
    var Name = Destination.Name;
    var T = Destination.PropertyType;
    var Value = JsonConvert.DeserializeObject("\"" + JT[Name] + "\"", T);
    Destination.SetValue(null, Value);
}
Thomas
  • 10,933
  • 14
  • 65
  • 136
  • Show the code in a [mcve] so that we can get a better understanding of the problem. – Nkosi May 15 '18 at 00:41
  • 3
    You don't need to use reflection directly, just use dynamic JSON parsing such as with `JToken.Parse`. Then manually assign the fields – Camilo Terevinto May 15 '18 at 00:41
  • I added more details – Thomas May 15 '18 at 00:45
  • @Camilo, I can parse the json with the jtoken.parse method, but how do I cast each value to the type of the matching field in the static class? – Thomas May 15 '18 at 00:49
  • https://stackoverflow.com/q/8074070/3254405 – boateng May 15 '18 at 01:03
  • @numbtongue: I don't get the link with the question? even if the static fields are wrapped inside a class instance, the issue remains. – Thomas May 15 '18 at 01:08
  • You can then desirialize JSON into array of outer objects and access the inner static class via constructor/methods, just an idea.. You trying to assign a list into single instance which may not be possible. – boateng May 15 '18 at 01:14

1 Answers1

5

You can do this quite easily by having a matching non-static class, getting the properties of source and destination and looping through each one. For example, assuming we have two classes:

public static class A
{
    public static int I { get; set; }
    public static string S { get; set; }
}

public class B
{
    public int I { get; set; }
    public string S { get; set; }
}

We can now do this:

public void MapToStaticClass(B source)
{
    var sourceProperties = source.GetType().GetProperties();

    //Key thing here is to specify we want the static properties only
    var destinationProperties = typeof(A)
        .GetProperties(BindingFlags.Public | BindingFlags.Static);

    foreach (var prop in sourceProperties)
    {
        //Find matching property by name
        var destinationProp = destinationProperties
            .Single(p => p.Name == prop.Name);

        //Set the static property value
        destinationProp.SetValue(null, prop.GetValue(source));
    }
}

Another option is to deserialise to JToken and use that combined with reflection:

var source = JsonConvert.DeserializeObject<JToken>(json);

And then:

public void MapJTokenToStaticClass(JToken source)
{
    var destinationProperties = typeof(A)
        .GetProperties(BindingFlags.Public | BindingFlags.Static);

    foreach (JProperty prop in source)
    {
        var destinationProp = destinationProperties
            .SingleOrDefault(p => p.Name.Equals(prop.Name, StringComparison.OrdinalIgnoreCase));
        var value = ((JValue)prop.Value).Value;

        //The ChangeType is required because JSON.Net will deserialise
        //numbers as long by default
        destinationProp.SetValue(null, Convert.ChangeType(value, destinationProp.PropertyType));
    }
}
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • That's very specifically what I'm trying to avoid because fields in this class get added as the projects sharing the lib need; otherwise I would have written a custom deserializer and the problem would have been solved. But I need to come up with a reflection based method to accommodate changes I don't have control over. – Thomas May 15 '18 at 00:56
  • I mentioned it needed to use reflection, but maybe I should have explained why. I'll edit the question – Thomas May 15 '18 at 00:59
  • Yes, this answer uses reflection! – DavidG May 15 '18 at 01:01
  • you're right, the question lacked clarity; hopefully this is fixed now. – Thomas May 15 '18 at 01:02
  • Well, I added another option for you. – DavidG May 15 '18 at 01:12
  • I came up with something similar, but yours is better. I'll mark yours as the answer and write what I came up with in the question. Thanks! – Thomas May 15 '18 at 01:18
  • actually in the end, ChangeType is failing where the deserialization works because it can't convert types like TimeSpan, etc, but for the simple types, it's probably much faster. – Thomas May 15 '18 at 01:29