0

I ran into a modelling problem while solving an assignment of mine. Say I have json string that I need to parse, process it in some way and serialize it back to json. The problem is this json contains arrays that hold different types of variables: strings, integers, booleans, decimals and formulas expressed as strings. The type of the value is known at object creation time (when parsing). How could I model my class in a way that would allow me to have an array of different types?

What I've tried
I thought of doing a class with an Object Value property and a Type Type property that would allow the user of the class to cast this value at runtime. There's a lot of answers on SO suggesting this but this does not seem like the best practice at all. What I've also thought of doing is to have a class with properties of all the needed different types where all of them are null except the actual value and some sort of enum representing which value is actually stored as suggested in this answer. However the Value property is still of type object which doesn't seem that good.

Example json : [15, "hello", 32, 11.12, "ADD(0, 2)"]. The numbers in formulas represent indeces of values in the array. This json should be converted to [15, "hello", 32, 11.12, 47].

What would be the best practice to approach this in C#?

14
  • 2
    All objects (including fundamental/primitive types like int) already know their own Type and have a .GetType() method, so I don't see any reason to duplicate that information, You may as well simply use List<Object>. Apr 13, 2023 at 22:28
  • @BenCottrell: The case in question starts from serialized content, and the first step of the challenge is to deserialize it. Your comment doesn't quite apply to that scenario. The deserializer still has to know what it deserializes it into. (I'm skipping some finer points because I'm not trying to answer the question but rather tackling why your comment oversimplifies the potential solution)
    – Flater
    Apr 14, 2023 at 5:19
  • 2
    This smells like an XY-Problem. Maybe tell us what you want to achieve. From a first glance, I would say that's horrible JSON and the solution is to build it differently.
    – nvoigt
    Apr 14, 2023 at 8:46
  • What I need to do is deserialize this json into an array of some class. I need suggestions on how to model this class. The json structure and format is fixed and not up to me to change. Apr 14, 2023 at 10:29
  • Are you expected to handle the case where you get [15, "hello", 32, 11.12, "ADD(0, 4)"]?
    – Caleth
    Apr 14, 2023 at 15:15

3 Answers 3

2

The best way is to parse it into a list of objects (List<object>). You can use Newtonsoft.Json for that:

var parsed = JsonConvert.DeserializeObject<List<object>>(
               "[15, 'hello', 32, 11.12, 'ADD(0, 2)']")

Objects in C# contain their own type information at runtime, so you don't need a separate field for type. E.g. if you want to process all strings, you can just write:

 foreach (var item in parsed) {
   if (item is string str) {
     ...
   }
 }

You hint that you don't like using object as type. But using object is exactly the right thing to do here, since you don't know the type at compile time, and object is the closest common ancestor of all the types involved.

1
  • 1
    You could mention that it is possible to use a switch statement/expression to check multiple types, to avoid repeated if-statements. Or to use LINQ: .OfType<T>() to get all objects of some specific type.
    – JonasH
    May 15, 2023 at 9:07
0

You need a two step process: first identify the formulas, then substitute the values into the formulas.

We have an interface which can be either plain data or a formula. Evaluate will return the value we stored, or evaluate the formula.

public interface Value
{
    public dynamic Evaluate(List<Value> values);
}

And a class for plain data

public record class Atom(dynamic data) : Value
{
    public dynamic Evaluate(List<Value> values) => data;
}

Each formula is it's own class

public record class Add(int first, int second) : Value
{
    public dynamic Evaluate(List<Value> values) 
    {
        var v1 = values[first].Evaluate(values);
        var v2 = values[second].Evaluate(values);
        return v1 + v2;
    }
}

We need to find which values are formulas

public static Value Parse(dynamic v)
{
    if (v is String) {
        Match m = Regex.Match(v, @"(\w+)\((\d+), (\d+)\)");
        if (m.Success) {
            var lhs = Int32.Parse(m.Groups[2].Value);
            var rhs = Int32.Parse(m.Groups[3].Value);
            switch (m.Groups[1].Value) {
                case "ADD": return new Add(lhs, rhs);
                // more formulas here
            }
        }
    }
    return new Atom(v);
}

And then bring that all together

var parsed = JsonConvert.DeserializeObject<List<dynamic>>("[15, 'hello', 32, 11.12, 'ADD(0, 2)']")
    .Select<dynamic, Value>(Parse)
    .ToList();
var evaluated = parsed.Select(p => p.Evaluate(parsed)).ToList();

C# fiddle

-1

Yes, you are correct that to achieve proper polymorphism, you would need to have a common property in the base class that is overridden in the child classes. However, having a property of type object in the base class can be problematic because it can lead to runtime errors due to invalid type conversions or null reference exceptions.

One way to avoid this issue is to use a generic abstract class with a type parameter that represents the specific value type, like this:

public abstract class ValueCell<T>
{
    public abstract T Value { get; }
}

Then, you can create concrete subclasses that implement the abstract Value property with the specific value type, like this:

public class StringValueCell : ValueCell<string>
{
    private readonly string _value;

    public StringValueCell(string value)
    {
        _value = value;
    }

    public override string Value => _value;
}

public class IntValueCell : ValueCell<int>
{
    private readonly int _value;

    public IntValueCell(int value)
    {
        _value = value;
    }

    public override int Value => _value;
}

// and so on for other value types...

With this approach, you can still use a strongly typed collection to store instances of the ValueCell classes, and you can access the specific value type through the abstract Value property without the need for casting or risking runtime errors.

I hope this helps! and regarding the link, it works fine you can see:

Link is working

3
  • This wouldn't work as I would have to keep an array of the type ValueCell<T> and substitute the T for 1 type only. I would have to choose either ValueCell<int>[] or ValueCell<bool>[] but not every type at the sime time Apr 14, 2023 at 18:43
  • 1
    @Rehes Rarba - just for info, since you are a new contributor: this site does not work as a chat forum (that is, this page is not conceptualized as a thread of messages). This is a Q&A site, and each answer should be self-contained. The different answers are not shown in chronological order, but float up or down based on votes. So, when responding to the askers comments to your answer, rather than posting another answer, you should edit your existing answer to add in the new information. Apr 15, 2023 at 14:23
  • Thanks for the clarification. Apr 17, 2023 at 8:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.