Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QSettings: inferring type of stored value
Qt 6.11 is out! See what's new in the release blog

QSettings: inferring type of stored value

Scheduled Pinned Locked Moved Solved General and Desktop
17 Posts 4 Posters 2.0k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Axel SpoerlA Axel Spoerl

    As @Christian-Ehrlicher said, QSettings uses QVariant with all its power and limitations under the hood.
    When you experiment, it's important to know that QSettings buffers its QVariant value sin memory, before actually writing stuff on disk.
    If you register custom enum types, it will actually remember the exact type until it's written and read back from disk. That might be confusing when you debug.

    There are different ways to safely infer types to settings, but they require individual implementation.

    • One option is to encode enum types to a string value and parse them back. I have seen implementations along the lines of @@@ENUM::EnumName::EnumValue.
    • Another option, as said before, is to map keys to types - at the cost of hard coding.
    • The option I'd probably choose is to implement union class around your enums. The class is actually a small QVariant for custom enums. It can be constructed with a value of any of the enums needed. A type() getter tells which enum it actually holds. QDataStream operators, or a toByteArray() / fromByteArray() implementation will eventually stream the content into a byte array that can be saved in the settings. You can implement a cast and / or a toEnum<Type>() template function. You probably want to start the data stream or the byte array with some magic numbers, so your union class can tell if it likes a byte array or not. Sounds complicated, but it's actually just a thin wrapper.
    mzimmersM Offline
    mzimmersM Offline
    mzimmers
    wrote on last edited by
    #6

    @Axel-Spoerl thanks for the detailed reply. I'd like to back up for a moment, as I seem to be missing something here.

    I've implemented a wrapper class around QSettings. (Normally I'd post code in code blocks, but in this case I think an image will be more informative.) Notice that despite my attempting to store a bool setting, when I read it back, it comes back as a QString.
    Screenshot 2024-08-25 095020.png
    Am I doing something wrong, or do I simply need to follow your second option in order to derive the type?

    Thanks...

    1 Reply Last reply
    0
    • Christian EhrlicherC Offline
      Christian EhrlicherC Offline
      Christian Ehrlicher
      Lifetime Qt Champion
      wrote on last edited by
      #7

      It's working fine here:

      QSettings s("D:\\test.ini", QSettings::IniFormat);
      s.setValue("mybool", true);
      qDebug() << s.value("mybool");
      

      --> QVariant(bool, true)

      So I would say the debugger is simply converting the bool to a user-visible value since QVariant<bool>() can be properly converted to a QString.

      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
      Visit the Qt Academy at https://academy.qt.io/catalog

      Axel SpoerlA 1 Reply Last reply
      0
      • Christian EhrlicherC Christian Ehrlicher

        It's working fine here:

        QSettings s("D:\\test.ini", QSettings::IniFormat);
        s.setValue("mybool", true);
        qDebug() << s.value("mybool");
        

        --> QVariant(bool, true)

        So I would say the debugger is simply converting the bool to a user-visible value since QVariant<bool>() can be properly converted to a QString.

        Axel SpoerlA Offline
        Axel SpoerlA Offline
        Axel Spoerl
        Moderators
        wrote on last edited by
        #8

        @Christian-Ehrlicher
        @mzimmers

        At debugging time, the variant is still in memory. Not sure, but my guess is that the debugging output is different, when you read the settings right awawy in the class constructor.

        Software Engineer
        The Qt Company, Oslo

        1 Reply Last reply
        0
        • mzimmersM Offline
          mzimmersM Offline
          mzimmers
          wrote on last edited by
          #9

          @Axel-Spoerl @Christian-Ehrlicher I've moved some code around:

          class NgaSettings : public QObject
          {
              Q_OBJECT
              QML_ELEMENT
              QSettings *m_settings = new QSettings("company", "nga", this);
              
          public:
              explicit NgaSettings(QObject *parent = nullptr) {
                  m_settings->clear();
                  m_settings->setValue("name", "Mike");
                  m_settings->setValue("age", "65");
                  m_settings->setValue("city", "Salinas");
                  m_settings->setValue("is golfing today", false);
          }
              INVOKABLE void getType(const QVariant &v) {
                  if (v.canConvert<QString>()) {
                      qDebug() << "QString";
                  } else if (v.canConvert<bool>()) {
                      qDebug() << "bool";
                  } else {
                      qDebug() << "unknown";
                  }
              }
          

          And I test it in QML:

              Component.onCompleted: {
                  console.log("SettingsList.qml: type is " + ngaSettings.getType("is golfing today"))
              }
          

          getType returns true on the test for QString. I don't think re-ordering the test will help, as it appears that any non-null QString will convert to a (true) bool.

          So, I'm back to thinking that I have to maintain a map of keys to types.

          JonBJ 1 Reply Last reply
          0
          • Christian EhrlicherC Offline
            Christian EhrlicherC Offline
            Christian Ehrlicher
            Lifetime Qt Champion
            wrote on last edited by
            #10

            What do you expect - as I said a bool can be converted to a string. You have to check the type of QVariant...

            Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
            Visit the Qt Academy at https://academy.qt.io/catalog

            1 Reply Last reply
            0
            • Axel SpoerlA Offline
              Axel SpoerlA Offline
              Axel Spoerl
              Moderators
              wrote on last edited by Axel Spoerl
              #11

              QSettings out of the box expects the user to know the type of each key.
              When the settings object is constructed and key/value pairs are added, they are actually stored in a list in memory and remember their original type.
              Custom enums become integers.
              Once read back from the settings file, everything (numbers included) will be interpreted as a string by default.

              Example:

              MainWindow::MainWindow(QWidget *parent)
                  : QMainWindow(parent)
                  , ui(new Ui::MainWindow)
              {
                  QSettings s("/home/axel/tmp/settings.conf", QSettings::Format::NativeFormat);
                  qDebug() << "----------- after write -----------";
                  qDebug() << "One" << s.value("One");
                  qDebug() << "Two" << s.value("Two");
                  qDebug() << "Three" << s.value("Three");
                  qDebug() << "Boolean" << s.value("Boolean");
              }
              
              MainWindow::~MainWindow()
              {
                  QSettings s("/home/axel/tmp/settings.conf", QSettings::Format::NativeFormat);
                  s.setValue("One", Value1);
                  s.setValue("Two", IllegalValue);
                  s.setValue("Three", Dummy);
                  s.setValue("Boolean", true);
                  qDebug() << "----------- before write -----------";
                  qDebug() << "One" << s.value("One");
                  qDebug() << "Two" << s.value("Two");
                  qDebug() << "Three" << s.value("Three");
                  qDebug() << "Boolean" << s.value("Boolean");
                  delete ui;
              }
              

              Output:

              ----------- after write -----------
              One QVariant(QString, "0")
              Two QVariant(QString, "1")
              Three QVariant(QString, "2")
              Boolean QVariant(QString, "true")
              ----------- before write -----------
              One QVariant(int, 0)
              Two QVariant(int, 1)
              Three QVariant(int, 2)
              Boolean QVariant(bool, true)
              

              Software Engineer
              The Qt Company, Oslo

              mzimmersM 1 Reply Last reply
              0
              • mzimmersM mzimmers

                @Axel-Spoerl @Christian-Ehrlicher I've moved some code around:

                class NgaSettings : public QObject
                {
                    Q_OBJECT
                    QML_ELEMENT
                    QSettings *m_settings = new QSettings("company", "nga", this);
                    
                public:
                    explicit NgaSettings(QObject *parent = nullptr) {
                        m_settings->clear();
                        m_settings->setValue("name", "Mike");
                        m_settings->setValue("age", "65");
                        m_settings->setValue("city", "Salinas");
                        m_settings->setValue("is golfing today", false);
                }
                    INVOKABLE void getType(const QVariant &v) {
                        if (v.canConvert<QString>()) {
                            qDebug() << "QString";
                        } else if (v.canConvert<bool>()) {
                            qDebug() << "bool";
                        } else {
                            qDebug() << "unknown";
                        }
                    }
                

                And I test it in QML:

                    Component.onCompleted: {
                        console.log("SettingsList.qml: type is " + ngaSettings.getType("is golfing today"))
                    }
                

                getType returns true on the test for QString. I don't think re-ordering the test will help, as it appears that any non-null QString will convert to a (true) bool.

                So, I'm back to thinking that I have to maintain a map of keys to types.

                JonBJ Offline
                JonBJ Offline
                JonB
                wrote on last edited by
                #12

                @mzimmers
                I find the way you are going about things slightly odd. Usually in the case of QSettings where you are saving/restoring named "settings/variables" you know the type of the item. E.g. if you have a width variable/member/whatever it's going to be a number, if it's a last_modified it's going to be a QDateTime, etc. When you read back you know from the name which variable you're going to store the read value in so you know what type to ask for (even if you call canConvert<>() to be sure). There are not so many obvious use cases where you would need to look at the (unknown) type of a setting read in, if you don't know the type in advance what are you going to do with whatever you get anyway?

                mzimmersM 1 Reply Last reply
                0
                • Axel SpoerlA Offline
                  Axel SpoerlA Offline
                  Axel Spoerl
                  Moderators
                  wrote on last edited by
                  #13

                  @mzimmers
                  Maybe you want to split the implementation. It looks as if a variant map is what you need for the user to view, modify, add and remove key/value pairs. Another question is how to save / restore it. Could be in a database, a proprietary file format, or streamed into a byte array and saved in settings.

                  Software Engineer
                  The Qt Company, Oslo

                  1 Reply Last reply
                  0
                  • Axel SpoerlA Axel Spoerl

                    QSettings out of the box expects the user to know the type of each key.
                    When the settings object is constructed and key/value pairs are added, they are actually stored in a list in memory and remember their original type.
                    Custom enums become integers.
                    Once read back from the settings file, everything (numbers included) will be interpreted as a string by default.

                    Example:

                    MainWindow::MainWindow(QWidget *parent)
                        : QMainWindow(parent)
                        , ui(new Ui::MainWindow)
                    {
                        QSettings s("/home/axel/tmp/settings.conf", QSettings::Format::NativeFormat);
                        qDebug() << "----------- after write -----------";
                        qDebug() << "One" << s.value("One");
                        qDebug() << "Two" << s.value("Two");
                        qDebug() << "Three" << s.value("Three");
                        qDebug() << "Boolean" << s.value("Boolean");
                    }
                    
                    MainWindow::~MainWindow()
                    {
                        QSettings s("/home/axel/tmp/settings.conf", QSettings::Format::NativeFormat);
                        s.setValue("One", Value1);
                        s.setValue("Two", IllegalValue);
                        s.setValue("Three", Dummy);
                        s.setValue("Boolean", true);
                        qDebug() << "----------- before write -----------";
                        qDebug() << "One" << s.value("One");
                        qDebug() << "Two" << s.value("Two");
                        qDebug() << "Three" << s.value("Three");
                        qDebug() << "Boolean" << s.value("Boolean");
                        delete ui;
                    }
                    

                    Output:

                    ----------- after write -----------
                    One QVariant(QString, "0")
                    Two QVariant(QString, "1")
                    Three QVariant(QString, "2")
                    Boolean QVariant(QString, "true")
                    ----------- before write -----------
                    One QVariant(int, 0)
                    Two QVariant(int, 1)
                    Three QVariant(int, 2)
                    Boolean QVariant(bool, true)
                    
                    mzimmersM Offline
                    mzimmersM Offline
                    mzimmers
                    wrote on last edited by mzimmers
                    #14

                    @Axel-Spoerl said in QSettings: inferring type of stored value:

                    Once read back from the settings file, everything (numbers included) will be interpreted as a string by default.

                    Aha...I wasn't aware of this, and this explains why much of what I was trying wasn't working.

                    I notice you said "by default." Does this mean this can be changed from within the QSettings class, or do I need to handle this myself?

                    Regarding your suggestion of splitting the implementation, I think that's exactly what's necessary.

                    Thanks!

                    1 Reply Last reply
                    0
                    • JonBJ JonB

                      @mzimmers
                      I find the way you are going about things slightly odd. Usually in the case of QSettings where you are saving/restoring named "settings/variables" you know the type of the item. E.g. if you have a width variable/member/whatever it's going to be a number, if it's a last_modified it's going to be a QDateTime, etc. When you read back you know from the name which variable you're going to store the read value in so you know what type to ask for (even if you call canConvert<>() to be sure). There are not so many obvious use cases where you would need to look at the (unknown) type of a setting read in, if you don't know the type in advance what are you going to do with whatever you get anyway?

                      mzimmersM Offline
                      mzimmersM Offline
                      mzimmers
                      wrote on last edited by
                      #15

                      @JonB the intention was to use a ListView, using the QSettings keys as a model, to construct a list of settings for the user to view and alter. If I'm understanding @Axel-Spoerl correctly, though, this would be unwieldy at best. Looks like I need to make that mapping, and use those keys as my model. Not sure how I'll handle this in the QML delegate, but that's tomorrow's problem.

                      Axel SpoerlA 1 Reply Last reply
                      0
                      • mzimmersM mzimmers

                        @JonB the intention was to use a ListView, using the QSettings keys as a model, to construct a list of settings for the user to view and alter. If I'm understanding @Axel-Spoerl correctly, though, this would be unwieldy at best. Looks like I need to make that mapping, and use those keys as my model. Not sure how I'll handle this in the QML delegate, but that's tomorrow's problem.

                        Axel SpoerlA Offline
                        Axel SpoerlA Offline
                        Axel Spoerl
                        Moderators
                        wrote on last edited by
                        #16

                        @mzimmers
                        Looks like your users will ultimately modify the preferences of the application they use.
                        I really like the idea of doing that in a list view with a model. Totally cool. The model could actually serve the application to query its preferences.
                        I'd simply add data stream operators or toByteArray() / fromByteArray() helpers to store everything in QSettings ultimately.
                        But i'd not wrap the model around a QSettings object, as you already said.
                        The model could even be more powerful and allow/disallow preference modifications in certain states of the application, or fire signals if a specific preference has been changed - so the application can react.
                        Exposing that stuff to QML shouldn't be difficult. Let us know if help is needed there.
                        And thanks for bringing the topic up - I find it inspiring.

                        Software Engineer
                        The Qt Company, Oslo

                        1 Reply Last reply
                        0
                        • Axel SpoerlA Axel Spoerl

                          As @Christian-Ehrlicher said, QSettings uses QVariant with all its power and limitations under the hood.
                          When you experiment, it's important to know that QSettings buffers its QVariant value sin memory, before actually writing stuff on disk.
                          If you register custom enum types, it will actually remember the exact type until it's written and read back from disk. That might be confusing when you debug.

                          There are different ways to safely infer types to settings, but they require individual implementation.

                          • One option is to encode enum types to a string value and parse them back. I have seen implementations along the lines of @@@ENUM::EnumName::EnumValue.
                          • Another option, as said before, is to map keys to types - at the cost of hard coding.
                          • The option I'd probably choose is to implement union class around your enums. The class is actually a small QVariant for custom enums. It can be constructed with a value of any of the enums needed. A type() getter tells which enum it actually holds. QDataStream operators, or a toByteArray() / fromByteArray() implementation will eventually stream the content into a byte array that can be saved in the settings. You can implement a cast and / or a toEnum<Type>() template function. You probably want to start the data stream or the byte array with some magic numbers, so your union class can tell if it likes a byte array or not. Sounds complicated, but it's actually just a thin wrapper.
                          mzimmersM Offline
                          mzimmersM Offline
                          mzimmers
                          wrote on last edited by
                          #17

                          @Axel-Spoerl said in QSettings: inferring type of stored value:

                          One option is to encode enum types to a string value and parse them back. I have seen implementations along the lines of @@@ENUM::EnumName::EnumValue.

                          This is how I ended up doing it. Rather primitive, but effective.

                          Thanks to all who contributed to this topic.

                          1 Reply Last reply
                          1
                          • mzimmersM mzimmers has marked this topic as solved on

                          • Login

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved