[PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject

Paolo Bonzini posted 19 patches 4 months ago
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, "Daniel P. Berrangé" <berrange@redhat.com>, "Philippe Mathieu-Daudé" <philmd@linaro.org>, Markus Armbruster <armbru@redhat.com>, Michael Roth <michael.roth@amd.com>, Manos Pitsidianakis <manos.pitsidianakis@linaro.org>, "Alex Bennée" <alex.bennee@linaro.org>, Thomas Huth <thuth@redhat.com>
There is a newer version of this series
[PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject
Posted by Paolo Bonzini 4 months ago
These are used by tests.  However it could even be an idea to use
serde_json + transcoding and get rid of the C version...

Co-authored-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 rust/util/wrapper.h          |  1 +
 rust/util/src/qobject/mod.rs | 17 +++++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/rust/util/wrapper.h b/rust/util/wrapper.h
index 0907dd59142..c88820a5e5b 100644
--- a/rust/util/wrapper.h
+++ b/rust/util/wrapper.h
@@ -37,3 +37,4 @@ typedef enum memory_order {
 #include "qobject/qobject.h"
 #include "qobject/qlist.h"
 #include "qobject/qdict.h"
+#include "qobject/qjson.h"
diff --git a/rust/util/src/qobject/mod.rs b/rust/util/src/qobject/mod.rs
index e896aba5f3a..292a3c9c238 100644
--- a/rust/util/src/qobject/mod.rs
+++ b/rust/util/src/qobject/mod.rs
@@ -23,6 +23,7 @@
 use common::assert_field_type;
 pub use deserializer::from_qobject;
 pub use error::{Error, Result};
+use foreign::prelude::*;
 pub use serializer::to_qobject;
 
 use crate::bindings;
@@ -111,6 +112,22 @@ fn refcnt(&self) -> &AtomicUsize {
         let qobj = self.0.get();
         unsafe { AtomicUsize::from_ptr(addr_of_mut!((*qobj).base.refcnt)) }
     }
+
+    pub fn to_json(&self) -> String {
+        let qobj = self.0.get();
+        unsafe {
+            let json = bindings::qobject_to_json(qobj);
+            glib_sys::g_string_free(json, glib_sys::GFALSE).into_native()
+        }
+    }
+
+    pub fn from_json(json: &str) -> std::result::Result<Self, crate::Error> {
+        let c_json = std::ffi::CString::new(json)?;
+        unsafe {
+            crate::Error::with_errp(|errp| bindings::qobject_from_json(c_json.as_ptr(), errp))
+                .map(|qobj| QObject::from_raw(qobj))
+        }
+    }
 }
 
 impl From<()> for QObject {
-- 
2.51.0


Re: [PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject
Posted by Markus Armbruster 2 months ago
Paolo Bonzini <pbonzini@redhat.com> writes:

> These are used by tests.  However it could even be an idea to use
> serde_json + transcoding and get rid of the C version...

Tell me more!

> Co-authored-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  rust/util/wrapper.h          |  1 +
>  rust/util/src/qobject/mod.rs | 17 +++++++++++++++++
>  2 files changed, 18 insertions(+)
>
> diff --git a/rust/util/wrapper.h b/rust/util/wrapper.h
> index 0907dd59142..c88820a5e5b 100644
> --- a/rust/util/wrapper.h
> +++ b/rust/util/wrapper.h
> @@ -37,3 +37,4 @@ typedef enum memory_order {
>  #include "qobject/qobject.h"
>  #include "qobject/qlist.h"
>  #include "qobject/qdict.h"
> +#include "qobject/qjson.h"
> diff --git a/rust/util/src/qobject/mod.rs b/rust/util/src/qobject/mod.rs
> index e896aba5f3a..292a3c9c238 100644
> --- a/rust/util/src/qobject/mod.rs
> +++ b/rust/util/src/qobject/mod.rs
> @@ -23,6 +23,7 @@
>  use common::assert_field_type;
>  pub use deserializer::from_qobject;
>  pub use error::{Error, Result};
> +use foreign::prelude::*;
>  pub use serializer::to_qobject;
>  
>  use crate::bindings;
> @@ -111,6 +112,22 @@ fn refcnt(&self) -> &AtomicUsize {
>          let qobj = self.0.get();
>          unsafe { AtomicUsize::from_ptr(addr_of_mut!((*qobj).base.refcnt)) }
>      }
> +
> +    pub fn to_json(&self) -> String {
> +        let qobj = self.0.get();
> +        unsafe {
> +            let json = bindings::qobject_to_json(qobj);
> +            glib_sys::g_string_free(json, glib_sys::GFALSE).into_native()
> +        }
> +    }
> +
> +    pub fn from_json(json: &str) -> std::result::Result<Self, crate::Error> {
> +        let c_json = std::ffi::CString::new(json)?;
> +        unsafe {
> +            crate::Error::with_errp(|errp| bindings::qobject_from_json(c_json.as_ptr(), errp))
> +                .map(|qobj| QObject::from_raw(qobj))
> +        }
> +    }
>  }
>  
>  impl From<()> for QObject {
Re: [PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject
Posted by Paolo Bonzini 2 months ago
On 12/5/25 11:04, Markus Armbruster wrote:
> Paolo Bonzini <pbonzini@redhat.com> writes:
> 
>> These are used by tests.  However it could even be an idea to use
>> serde_json + transcoding and get rid of the C version...
> 
> Tell me more!

QEMU's JSON parser produces a QObject.  To obtain the same effect, we 
can take JSON-string-to-serde deserialization (implemented by 
serde_json) and attach it to serde-to-QObject serialization (the thing 
in patch 5).  That results in a JSON-string-to-QObject function.

Doing it in the other direction (QObject deserializer + JSON-string 
serializer) produces a QObject-to-JSON-string function.

For a little more information see https://serde.rs/transcode.html.

Note however that there is no support for push parsing, therefore this 
would not replace the balanced-parentheses machinery in 
qobject/json-streamer.c, and therefore QMP would still need a minimal lexer.

Grr... I just remembered about interpolation :/ so no, we still need a 
parser for libqmp.c.

Paolo

>> Co-authored-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> ---
>>   rust/util/wrapper.h          |  1 +
>>   rust/util/src/qobject/mod.rs | 17 +++++++++++++++++
>>   2 files changed, 18 insertions(+)
>>
>> diff --git a/rust/util/wrapper.h b/rust/util/wrapper.h
>> index 0907dd59142..c88820a5e5b 100644
>> --- a/rust/util/wrapper.h
>> +++ b/rust/util/wrapper.h
>> @@ -37,3 +37,4 @@ typedef enum memory_order {
>>   #include "qobject/qobject.h"
>>   #include "qobject/qlist.h"
>>   #include "qobject/qdict.h"
>> +#include "qobject/qjson.h"
>> diff --git a/rust/util/src/qobject/mod.rs b/rust/util/src/qobject/mod.rs
>> index e896aba5f3a..292a3c9c238 100644
>> --- a/rust/util/src/qobject/mod.rs
>> +++ b/rust/util/src/qobject/mod.rs
>> @@ -23,6 +23,7 @@
>>   use common::assert_field_type;
>>   pub use deserializer::from_qobject;
>>   pub use error::{Error, Result};
>> +use foreign::prelude::*;
>>   pub use serializer::to_qobject;
>>   
>>   use crate::bindings;
>> @@ -111,6 +112,22 @@ fn refcnt(&self) -> &AtomicUsize {
>>           let qobj = self.0.get();
>>           unsafe { AtomicUsize::from_ptr(addr_of_mut!((*qobj).base.refcnt)) }
>>       }
>> +
>> +    pub fn to_json(&self) -> String {
>> +        let qobj = self.0.get();
>> +        unsafe {
>> +            let json = bindings::qobject_to_json(qobj);
>> +            glib_sys::g_string_free(json, glib_sys::GFALSE).into_native()
>> +        }
>> +    }
>> +
>> +    pub fn from_json(json: &str) -> std::result::Result<Self, crate::Error> {
>> +        let c_json = std::ffi::CString::new(json)?;
>> +        unsafe {
>> +            crate::Error::with_errp(|errp| bindings::qobject_from_json(c_json.as_ptr(), errp))
>> +                .map(|qobj| QObject::from_raw(qobj))
>> +        }
>> +    }
>>   }
>>   
>>   impl From<()> for QObject {
> 
> 
> 


Re: [PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject
Posted by Markus Armbruster 2 months ago
Paolo Bonzini <pbonzini@redhat.com> writes:

> On 12/5/25 11:04, Markus Armbruster wrote:
>> Paolo Bonzini <pbonzini@redhat.com> writes:
>> 
>>> These are used by tests.  However it could even be an idea to use
>>> serde_json + transcoding and get rid of the C version...
>> 
>> Tell me more!
>
> QEMU's JSON parser produces a QObject.  To obtain the same effect, we 
> can take JSON-string-to-serde deserialization (implemented by 
> serde_json) and attach it to serde-to-QObject serialization (the thing 
> in patch 5).  That results in a JSON-string-to-QObject function.
>
> Doing it in the other direction (QObject deserializer + JSON-string 
> serializer) produces a QObject-to-JSON-string function.

Yes.

> For a little more information see https://serde.rs/transcode.html.
>
> Note however that there is no support for push parsing, therefore this 
> would not replace the balanced-parentheses machinery in 
> qobject/json-streamer.c, and therefore QMP would still need a minimal lexer.

That push parser...  I never liked it.  First, it's half-assed: it's a
push lexer wed to a pull parser with parenthesis counting.  Second, why
complicated & half-assed when you can do simple & quarter-assed instead?
We could've required "exactly one complete JSON value per line", or some
expression separator such as an empty line.

> Grr... I just remembered about interpolation :/ so no, we still need a 
> parser for libqmp.c.

Right.

Interpolation lets us build QObjects from literal templates with
variable scalars or QObjects interpolated.  More concise and much easier
to read than the equivalend nest of constructor calls.  Drawback: chains
us to our own, bespoke JSON parser.

Out of curiosity: how would we do better than "nest of constructor
calls" in Rust?
Re: [PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject
Posted by Paolo Bonzini 2 months ago
Il ven 5 dic 2025, 13:16 Markus Armbruster <armbru@redhat.com> ha scritto:

> > Note however that there is no support for push parsing, therefore this
> > would not replace the balanced-parentheses machinery in
> > qobject/json-streamer.c, and therefore QMP would still need a minimal
> lexer.
>
> That push parser...  I never liked it.  First, it's half-assed: it's a
> push lexer wed to a pull parser with parenthesis counting.  Second, why
> complicated & half-assed when you can do simple & quarter-assed instead?
> We could've required "exactly one complete JSON value per line", or some
> expression separator such as an empty line.
>

Hmm not sure I agree, actually I think I disagree. It seems simpler but it
is also different.

Push parsing is not rocket science. It would be easy to write a proper one,
it's just that there is no real reason other than cleanliness.

> Grr... I just remembered about interpolation :/ so no, we still need a
> > parser for libqmp.c.
>
> Right.
>
> Interpolation lets us build QObjects from literal templates with
> variable scalars or QObjects interpolated.  More concise and much easier
> to read than the equivalend nest of constructor calls.  Drawback: chains
> us to our own, bespoke JSON parser.
>

And also, for similar reasons of practicality, single quotes (which IIRC
also became valid QMP, and that's less excusable).

But while it's a pity, we still get a lot from serde, namely making the
automatic generation of visitor code for structs someone else's problem.

Out of curiosity: how would we do better than "nest of constructor
> calls" in Rust?
>

You'd do that with a quoting macro, i.e.

qobject!({"command": cmd})

where the macro compiles to the nest of constructor calls, like
QObject::from_iter([(c"command", to_qobject(cmd))]).

Paolo

>
Re: [PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject
Posted by Markus Armbruster 2 months ago
Paolo Bonzini <pbonzini@redhat.com> writes:

> Il ven 5 dic 2025, 13:16 Markus Armbruster <armbru@redhat.com> ha scritto:
>
>> > Note however that there is no support for push parsing, therefore this
>> > would not replace the balanced-parentheses machinery in
>> > qobject/json-streamer.c, and therefore QMP would still need a minimal
>> > lexer.
>>
>> That push parser...  I never liked it.  First, it's half-assed: it's a
>> push lexer wed to a pull parser with parenthesis counting.  Second, why
>> complicated & half-assed when you can do simple & quarter-assed instead?
>> We could've required "exactly one complete JSON value per line", or some
>> expression separator such as an empty line.
>
> Hmm not sure I agree, actually I think I disagree. It seems simpler but it
> is also different.

Management applications would be just fine with the different interface.

Human users would be better off.  As is, a missing right parenthesis is
met with silence.  You can then input whatever you want, and get more
silence until you somehow close the last parenthesis.  Quite confusing
unless you already know.

> Push parsing is not rocket science. It would be easy to write a proper one,
> it's just that there is no real reason other than cleanliness.

Me not liking what we have, when what we have works, is no reason to
rewrite it.

Even the lousy usability for humans doesn't justify a rewrite.

>> > Grr... I just remembered about interpolation :/ so no, we still need a
>> > parser for libqmp.c.
>>
>> Right.
>>
>> Interpolation lets us build QObjects from literal templates with
>> variable scalars or QObjects interpolated.  More concise and much easier
>> to read than the equivalend nest of constructor calls.  Drawback: chains
>> us to our own, bespoke JSON parser.
>
> And also, for similar reasons of practicality, single quotes (which IIRC
> also became valid QMP, and that's less excusable).

Single quotes should've been restricted to internal use, just like
interpolation.

> But while it's a pity, we still get a lot from serde, namely making the
> automatic generation of visitor code for structs someone else's problem.
>
>> Out of curiosity: how would we do better than "nest of constructor
>> calls" in Rust?
>
> You'd do that with a quoting macro, i.e.
>
> qobject!({"command": cmd})
>
> where the macro compiles to the nest of constructor calls, like
> QObject::from_iter([(c"command", to_qobject(cmd))]).

Thanks!
Re: [PATCH 11/19] rust/qobject: add from/to JSON bindings for QObject
Posted by Paolo Bonzini 2 months ago
On 12/8/25 10:17, Markus Armbruster wrote:
> Paolo Bonzini <pbonzini@redhat.com> writes:
> 
>> Il ven 5 dic 2025, 13:16 Markus Armbruster <armbru@redhat.com> ha scritto:
>>
>>>> Note however that there is no support for push parsing, therefore this
>>>> would not replace the balanced-parentheses machinery in
>>>> qobject/json-streamer.c, and therefore QMP would still need a minimal
>>>> lexer.
>>>
>>> That push parser...  I never liked it.  First, it's half-assed: it's a
>>> push lexer wed to a pull parser with parenthesis counting.  Second, why
>>> complicated & half-assed when you can do simple & quarter-assed instead?
>>> We could've required "exactly one complete JSON value per line", or some
>>> expression separator such as an empty line.
>>
>> Hmm not sure I agree, actually I think I disagree. It seems simpler but it
>> is also different.
> 
> Management applications would be just fine with the different interface.
> 
> Human users would be better off.  As is, a missing right parenthesis is
> met with silence.  You can then input whatever you want, and get more
> silence until you somehow close the last parenthesis.  Quite confusing
> unless you already know.
Not being able to add line breaks to a long JSON command (preparing it 
in an editor and pasting it into either a "-qmp stdio" terminal or 
socat/nc) would be a dealbreaker for me.

JSON says whitespace is irrelevant.  "This standard format but..." is a 
bad idea that requires extraordinary justification.  Not that it matters 
now 15 years down the line. :)

Paolo