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
Forum Updated to NodeBB v4.3 + New Features

QSettings: inferring type of stored value

Scheduled Pinned Locked Moved Solved General and Desktop
17 Posts 4 Posters 1.6k 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 Offline
    Axel SpoerlA Offline
    Axel Spoerl
    Moderators
    wrote on last edited by Axel Spoerl
    #5

    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.

    Software Engineer
    The Qt Company, Oslo

    mzimmersM 2 Replies Last reply
    2
    • 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 Online
        Christian EhrlicherC Online
        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 Online
              Christian EhrlicherC Online
              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