1

I have a custom post type which stores 2 meta fields. The first meta field is string data. No problem. The second is JSON data stored in field let's call it 'video'. For example I want to store in this field something like this: {key: data}. The Rest API should understand that this is JSON and return meta: {_string_field: "string", video: {key: data}}. In fact it returns: meta: {_string_field: "string", _video: '{key: data}'}. Notice the '. It treats it as a string.

OK. So I understand I need to register the meta field as an Object type. (Object means JSON). AND I need to provide a schema for my JSON. This is explained here under Object Meta Type.

So I follow the instructions and end up with something like this (in register_post_meta):

'show_in_rest' => array(
            'schema' => array(
                'type'       => 'object',
                'properties' => array(
                    'key' => array(
                        'type' => 'string',
                    ),
                  
                ),
            ),
        ),
        'single' => true,
        'type' => 'object',

However - what happens now is the Rest API returns: meta: {_string_field: 'abc', _video: null}

Where null is I want my JSON! What am I doing wrong? What should I be looking for?

Update:

One thing which is interesting about the documentation is it says this:

For instance, the following code sample registers a post meta field called “release” that accepts the given JSON data.

{
  "meta": {
    "release": {
      "version": "5.2",
      "artist": "Jaco"
    }
  }
}

This would imply that the whole data posted from the field should be like the above including 'meta' and the name of the field, 'video' in my case. I have tried this. Still null. Also tried just sending back my actual data without repeating the "meta" and fieldname wrappers. Still null.

It looks like a documentation error - i.e. the feature may exist but it doesn't work as documented.

Update 2

After a lot of digging I found where the problem lies.

In /wp-includes/rest-api.php there is some code which validates (I think) the data which is being prepared for REST output. I find this function: rest_validate_value_from_schema() which calls rest_validate_object_value_from_schema() which calls rest_is_object() with the value and checks that the return is an array - in rest_is_object() we have this:

if ( $maybe_object instanceof JsonSerializable ) {
    $maybe_object = $maybe_object->jsonSerialize();
}

The code correctly detects that my custom meta field is of type Object and it tries to validate it. The value tested is the one I enter for the custom field value e.g. "{"test":"data"}" however after the above code $maybe_object is still a string "{"test":"data"}". rest_validate_object_value_from_schema() needs it to be an array and so determines that it is invalid. It looks like either (most likely) the value is unexpected or the above code is incorrect.

(I'm not sure about the instanceof - isn't JsonSerializable an interace which should be implemented to return an array? But I am not really an advanced PHP developer so I need advice on this point).

I am not 100% convinced that Object types are in fact fully supported but most likely I am missing something.

For now going to use a string field and convert to JSON on the client.

0

1 Answer 1

2

I'm not exactly sure what are you really trying to do, but anyway, I hope these help?

  • If you send the meta value as an object, e.g. video: {"foo":"bla blah","bar":1234} in JS, then note that WordPress will serialize the value (or convert the object to a serialized string) before it's saved to the database.

    See update_metadata() which is used by the REST API when updating a meta.

  • So if you set the type to object, then you should send the meta value as an object (or that will evaluate as an object in WordPress/PHP), but as for how, it depends on how you're making the REST API request, but basically, make sure the request body uses the proper format and the request also needs to set the proper Content-Type header.

    E.g. Using the native fetch() function in JS:

    fetch( 'https://example.com/wp-json/wp/v2/posts', {
        method: 'POST',
        headers: {
            // be sure to send/use the correct Content-Type
            'Content-Type': 'application/json',
    
            // and (cookie) nonce
            'X-WP-Nonce': 'YOUR_NONCE_HERE'
        },
        body: JSON.stringify( { // send a JSON-encoded request body
            title: '\'video\' meta test',
            content: 'test via JS fetch()',
            meta: {
                video: { // pass an object
                    foo: 'bla blah',
                    bar: 1234
                }
            } // end meta
        } ) // end body
    } ) // wrapped
        .then( res => res.json() )
        .then( data => console.log( data.id, data.meta ) );
    

    Or using cURL command line and an application password:

    curl -X POST https://example.com/wp-json/wp/v2/posts --user "YOUR_USERNAME:YOUR_APPLICATION_PASSWORD" -H "Content-Type: application/json" -d "{\"title\":\"'video' meta test\",\"content\":\"test via curl\",\"meta\":{\"video\":{\"foo\":\"bla blah\",\"bar\":1234}}}"

  • If you actually wanted the meta value be saved as a JSON-encoded string, i.e. in the database, the value would look like {"foo":"bla blah","bar":1234} (instead of a serialized string), then you should set the type to string and send the meta value as a string, e.g. (note the single quotes, i.e. ') video: '{"foo":"bla blah","bar":1234}' in JS.

    But if you want the meta value be represented as an object in the REST API response (body), then you can use the prepare_callback argument (see WP_REST_Meta_Fields::prepare_value()) to convert the JSON string to an object, like so:

    register_post_meta(
        'post',  // post type
        'video', // meta key
        array(
            'single'       => true,
            'show_in_rest' => array(
                'type'             => 'string',
                'prepare_callback' => function ( $value ) {
                    return is_object( $value ) ? $value : json_decode( $value );
                },
            ),
        )
    );
    

    Just remember that you would need to, e.g. from a PHP template, manually convert the value to an object after fetching the value using get_post_meta() (or get_metadata()). E.g.

    $post_id = 123;
    $value   = get_post_meta( $post_id, 'video', true ); // true = gets a single value
    $video   = is_object( $value ) ? $value : json_decode( $value );
    
3
  • Thanks. I am sending the data from the WordPress admin site - it Posts it using multipart/form-data and this seems to be treated as a string. It gets saved in the db like this: {"foo":"bla blah","bar":1234}. When it retrieves it I now use your suggested approach - type is string and prepare_callback to json_decode it. Excellent. Your code for doing a pure Rest call without WP backend will help me in other cases probably. (I'm not sure if I sent it as a JSON object in the post body and set type to object what the serialised object in the db wld look like? Just the same as string perhaps?)
    – Kropotkin
    Mar 29, 2022 at 4:22
  • If you can show me the code you used (add it to your post/question or post on sites like Pastebin.com), I'd be able to tell you for sure what's wrong and the correct code that should be used. As for the other question (sent it as a JSON object), yes, the database value would still (and always) be a string, and would look like a:2:{s:3:"foo";s:8:"bla blah";s:3:"bar";i:1234;} - which WordPress automatically converts to an object (a PHP associative array) when retrieved using get_post_meta().
    – Sally CJ
    Mar 29, 2022 at 5:23
  • 1
    It is all good now. I use your code above with the prepare_callback. This ensures that the stringified json stored in the database is sent back in the standard WP Rest call as {"foo":"blah"} not '{"foo":"blah"}' so my JS client recognises it as a JSON literal and I can access it as an object. As I say - all good and the key was the callback when you register the Rest,
    – Kropotkin
    Mar 29, 2022 at 5:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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