Best practice to Extend Dictionary

I’d like to add some helper functions to the dictionary returned by the ParseJSON function.

Ideally I’d like to define a subclass of dictionary and be able to instantiate this subclass from the dictionary superclass returned by ParseJSON. We can do something similar with a folderitem but I can’t figure out how to do it with a dictionary.

What’s the best practice for this?

I don’t want to use Extension methods since these helpers are for JSON processing and don’t apply to generic dictionaries.

I’ve tried assigning the superclass instance to a variable of my subclass type (Illegal cast exception) and also passing the superclass dictionary instance into the constructor of my subclass. Neither work to create a usable dictionary with the original data. One potential solution seems to be in the Constructor to retain a reference to the superclass dictionary and overload any normal dictionary method I want to use and have them reference my reference to the superclass dictionary. But that’s a lot of extra methods to recreate, essentially unchanged.

How to create a subclass instance from a superclass instance? If we have an Animal superclass why can’t we now extend it with further details into a new instance of Dog subclass? Of course you can’t use a superclass instance as a subclass instance, but it sure seems like you should be able to create a subclass instance from a superclass instance. (Just like we can create a subclass definition from a superclass definition)

Any better ideas for adding methods to a Xojo builtin class?

You can either define a subclass and add methods to that, or you can use Extends methods on the original class. It depends what you want to change.

In terms of ParseJSON, I don’t think it returns a standard Dictionary. I think it is case-sensitive version of Dictionary. I’m not sure how that plays into the class names.

Subclass JSONItem then?

Hi Ian

When you say “define a subclass” I’m not sure exactly what you mean. I’d love to tell the ParseJSON Xojo global function to return an instance of my subclass but I’m not aware of that being possible. I’d prefer to not use Extends on the dictionary class for these narrowly useful JSON specific helpers.

Best I can tell ParseJSON returns a standard dictionary that apparently had the case sensitive comparison code delegate installed by its constructor. I think the dictionary itself is the standard dictionary class.

Tim, not sure how I’d use the JSONitem subclass unless I wanted to build the JSON document tree myself.???

Well, I did link the documentation page… You can pass a JSON string to the constructor or use the Load function. When your I/O is JSON it makes so much more sense to use JSONItem instead of Dictionary.

Hi Tim
Yes I’d looked at the docs before and just studied them again and wrote some test code based on the examples in the docs. And frankly I still don’t get how a JSONitem can be used to parse a whole JSON document. The docs do claim the Load method will parse a JSON string, but all that seems to do is to copy the string into the ToString property.

I don’t see how to use JSONitem to do any more than represent a single JSON node nor to actually do any JSON parsing at all. So what am I missing???

It works the same as the Dictionary methods, but handles things like arrays much better.

var jsExtended as new MyJSONSubclass
jsExtended.Load(sSourceString)

var sSomething as String = jsExtended.Lookup("key", "defaultValue")

Your subclass can feature the augmentation functions you wish to add to Dictionary. You won’t need a reference to a source object because the subclass holds the data.

Edit: If you mean you don’t see the individual items in the debugger, yes, that’s a known drawback. The data is still all there.

imo the Array handling outweighs the “see it in the debugger” because I tend to have the JSON open in BBEdit for reference. To me, it’s faster to read and find data that way.

Don’t you want a new on that instantiation, as it should likely be marked as the solution.

1 Like

Fix’d thanks.

OK This sounds very promising. I currently mostly have a collection of double arrays at the JSON root plus a couple of simple string properties.

For an array of JSON Numbers ParseJSON returns an array of variants where each element may be an integer or double type depending on whether the number has a decimal point or not. (The JSON serializer at the other end currently doesn’t send trailing zeros nor a decimal point for doubles with integer values)

Since there’s no debugger output, can you help me understand what exactly gets returned from a Lookup of an array of Numbers?

And in what ways do you find the array handling better… since that’s the main thing I need to do too.

The dictionary method leaves you using Introspection to handle arrays where JSONItem will provide another JSONItem to parse. If you know something is going to be an object or array, you can get a JSONItem with the Child function, otherwise use Lookup or Value to get a Variant.

If the item returned from Lookup or Value is an array, the return value will be a JSONItem containing the array. You can iterate the children of an array JSONItem like this:

for i as Integer = 0 to jsExtended.LastRowIndex
  var vValue as Variant = jsExtended.ValueAt(i)

next i

You will still need to test whether vValue is a double or integer with Variant.Type if that data is significant to you.

If you have a sample JSON string you can share, I can help demonstrate how to parse it.

Edit: Removed link to Value documentation - it’s obvious copypasta from Dictionary and is wrong for JSONItem

Tim Here’s the subclass’s function to extract a double array given a key name.

Public Function GetDoublesArray(KeyName as string) As double()
  Var theVarArray() As Variant = theJSON.Value(KeyName)
  Var theDoubles() As Double
  For Each VarItem As Variant In theVarArray
    theDoubles.Add(VarItem.DoubleValue)
  Next
  Return theDoubles
End Function

This approach is easy enough and works fine. I’d just like to better package it up into a dictionary subclass if only that were possible.

And I call it like so.

Var JsonDict As New JSONDictClass(ParseJSON(JSONtext))
Var rst As New ResultSpeedTableClass
rst.CaptureTimes() = JsonDict.GetDoublesArray("CaptureTime")
rst.OpticalPins() = JsonDict.GetDoublesArray("OpticalPin")
rst.TimeDeltas() = JsonDict.GetDoublesArray("TimeDelta")
rst.IntervalSpeeds() = JsonDict.GetDoublesArray("IntervalSpeed")
rst.IntervalSpeedDeltas() = JsonDict.GetDoublesArray("IntervalSpeedDelta")
rst.OverallSpeeds() = JsonDict.GetDoublesArray("OverallSpeed")

Here’s a sample JSON string:
{“DatasetType”:“ResultSpeedTable”,“CaptureTime”:[213,267,323,379,431,486,542,600,655,711,767,822,877,932,987,1042,1097,1152,1207,1262,1317,1372,1427,1485,1538,1595,1649,1704,1761,1817,1871,1928,1980,2038,2092,2148,2202,2258],“OpticalPin”:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],“TimeDelta”:[0,54,56,56,52,55,56,58,55,56,56,55,55,55,55,55,55,55,55,55,55,55,55,58,53,57,54,55,57,56,54,57,52,58,54,56,54,56],“IntervalSpeed”:[0,7217.851852,6960.071429,6960.071429,7495.461538,7086.618182,6960.071429,6720.068966,7086.618182,6960.071429,6960.071429,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,7086.618182,6720.068966,7354.037736,6837.964912,7217.851852,7086.618182,6837.964912,6960.071429,7217.851852,6837.964912,7495.461538,6720.068966,7217.851852,6960.071429,7217.851852,6960.071429],“IntervalSpeedDelta”:[0,0,-257.7804233,0,535.3901099,-408.8433566,-126.5467532,-240.0024631,366.5492163,-126.5467532,0,126.5467532,0,0,0,0,0,0,0,0,0,0,0,-366.5492163,633.9687703,-516.0728236,379.8869396,-131.23367,-248.6532695,122.1065163,257.7804233,-379.8869396,657.4966262,-775.3925729,497.7828863,-257.7804233,257.7804233,-257.7804233],“OverallSpeed”:[0,7217.851852,7086.618182,7043.927711,7151.633028,7138.534799,7108.158055,7049.994832,7054.552036,7043.927711,7035.451264,7040.07225,7043.927711,7047.193324,7049.994832,7052.424608,7054.552036,7056.430245,7058.100604,7059.595806,7060.942029,7062.160483,7063.268534,7047.619497,7059.876226,7050.723589,7057.008357,7058.100604,7049.994832,7046.855362,7052.424608,7045.296793,7058.544426,7047.787397,7052.674827,7049.994832,7054.552036,7051.964792]}

Here’s how I would do that extension with a JSONItem subclass: json.xojo_xml_project - 10kb

You can only cast classes upward, meaning you could only cast an instance of your dictionary subclass to dictionary (thus losing any subclass information). The Dictionary / ParseJSON system just isn’t going to work in this case.

1 Like

Hey Tim

Wow, that was fast. I’ll go study your approach. I really appreciate your insights and help.

Yeh, I don’t really want to cast the super into the sub class. But I’d sure like a dictionary subclass constructor that took a source dictionary like the folderitem does. I haven’t used that recently but it sure was handy back when I needed it.

Now I’m understanding your comparison to FolderItem. Unfortunately that doesn’t open up anything good for us. In theory, you could create a constructor on your subclass that accepts a dictionary, then iterate all the keys and copy them into the subclass instance. Not a fan of that idea either, really.

Yeh, I didn’t like the idea of all that copying either. My best prior solution was a subclass constructor that took a source dictionary parameter and held an internal reference to it. That’s fine for my custom methods but none of the standard dictionary methods knew to use my reference to the source dictionary and of course just accessed the empty internal dictionary. It’s temptingly simple (for naive me) to think the constructor ought to be able to substitute a reference to the source dictionary in place of the internal dictionary.. And hence allow a dictionary subclass to effectively wrap the source dictionary. But reality may be much harder than that.

Tim I’ve looked at your suggestion and it’s very clever. It seems like it would be a much better approach to accessing elemengs that aren’t at the root level. I still have to become familiar with what can and needs to be done from the root JSONitem vs the child ones.

Thanks so much for all your time, energy and insights.

2 Likes