6

I have two Json files that I exported from wordpress that have corresponding ID's I want to combine them into one Json file so I can bring it into website I am building with Gatsby JS. One of the files is the posts.json and the other is postsMeta.json. The post_id in postsMeta corresponds with the ID in Posts

How would I best go about merging the two? Can I run some sort of for loop in js and how would I so? I am on windows is there a json explorer of some sorts that could help me do this.

lastly I would also like to trim out some of the unnecasry fiels such as post_parent in the posts json and something like the meta_key in the postsMeta json.

Ok hopefully this is clear enough, thanks in advance.

Here is an example of the first object corresponding pairs in the two files

posts.json

{"ID":"19","post_author":"2","post_date":"2010-12-31 23:02:04","post_date_gmt":"2010-12-31 23:02:04","post_content":"Harry Potter was not available for the first sitting of the Halloween Picture. I hope everyone had a safe and fun Halloween. Tomorrow is picture retake day, please send back your previous prints if you want retakes. It is also hot lunch. See You tomorrow!","post_title":"Happy Halloween","post_excerpt":"","post_status":"publish","comment_status":"open","ping_status":"open","post_password":"","post_name":"happy-halloween","to_ping":"","pinged":"","post_modified":"2011-01-03 05:26:11","post_modified_gmt":"2011-01-03 05:26:11","post_content_filtered":"","post_parent":"0","guid":"http:\/\/localhost\/mrskitson.ca_wordpress\/?p=19","menu_order":"0","post_type":"post","post_mime_type":"","comment_count":"1"},

postsMeta.json

{"meta_id":"27","post_id":"19","meta_key":"large_preview","meta_value":"http:\/\/www.mrskitson.ca\/wp-content\/uploads\/2010\/12\/halloween.jpg"},

Update:

this is an attempt to solve this problem with the current answer, you can edit the code there.

Anders Kitson
  • 1,413
  • 6
  • 38
  • 98
  • Very interesting sample code your question produced in the answers below, +1 for everybody :) – brasofilo May 10 '18 at 06:08
  • Create a new json with the fields that you need, loop through the first json cheking every row in the second json, then when the keys match, fill the current 'row' in the new json, then start over the second 'row' in the first json. – WltrRpo May 11 '18 at 20:32

4 Answers4

6

How would I best go about merging the two?

Is it mandatory for you combine the two JSON files/data?

Because you could just require or load the JSON data from within your script (or even put them in the HTML) and then to get the meta value of a specific meta field/key, this function could do that:

// `single` has no effect if `meta_key` is empty.
function getPostMeta( post_id, meta_key, single ) {
    let id = String( post_id ),
        pm = [];
    postsMeta.map( m => {
        let a = ( ! meta_key ) ||
            ( meta_key === m.meta_key );

        if ( a && id === m.post_id ) {
            pm.push( m );
        }
    });

    let meta = {},
        mk = {};
    pm.map( m => {
        let k = m.meta_key, v;

        if ( undefined === meta[ k ] ) {
            meta[ k ] = m.meta_value;
        } else {
            v = meta[ k ];
            if ( undefined === mk[ k ] ) {
                meta[ k ] = [ v ];
                mk[ k ] = 1;
            }

            meta[ k ].push( m.meta_value );
            m[ k ]++;
        }
    });

    pm = null;
    mk = meta_key ? mk[ meta_key ] : null;

    if ( mk ) {
        return single ?
            meta[ meta_key ][0] : // Returns a single meta value.
            meta[ meta_key ];     // Returns all the meta values.
    }

    return meta_key ?
        meta[ meta_key ] : // Returns the value of the `meta_key`.
        meta;              // Or returns all the post's meta data.
}

The data I used for testing: (take note of the postsMeta in the above/getPostMeta() function)

// Array of `post` objects.
const posts = [{"ID":"19","post_author":"2","post_date":"2010-12-31 23:02:04","post_date_gmt":"2010-12-31 23:02:04","post_content":"Harry Potter was not available for the first sitting of the Halloween Picture. I hope everyone had a safe and fun Halloween. Tomorrow is picture retake day, please send back your previous prints if you want retakes. It is also hot lunch. See You tomorrow!","post_title":"Happy Halloween","post_excerpt":"","post_status":"publish","comment_status":"open","ping_status":"open","post_password":"","post_name":"happy-halloween","to_ping":"","pinged":"","post_modified":"2011-01-03 05:26:11","post_modified_gmt":"2011-01-03 05:26:11","post_content_filtered":"","post_parent":"0","guid":"http:\/\/localhost\/mrskitson.ca_wordpress\/?p=19","menu_order":"0","post_type":"post","post_mime_type":"","comment_count":"1"}];

// Array of `meta` objects.
const postsMeta = [{"meta_id":"27","post_id":"19","meta_key":"large_preview","meta_value":"http:\/\/www.mrskitson.ca\/wp-content\/uploads\/2010\/12\/halloween.jpg"},{"meta_id":"28","post_id":"19","meta_key":"many_values","meta_value":"http:\/\/facebook.com"},{"meta_id":"29","post_id":"19","meta_key":"many_values","meta_value":"http:\/\/twitter.com"},{"meta_id":"30","post_id":"19","meta_key":"many_values","meta_value":"http:\/\/linkedin.com"}];

Examples: (see this Fiddle for demo)

// In these examples, we are retrieving the meta value for the post #19 (i.e. ID is 19).

// Retrieve a single value.
// Returns mixed; string, number, etc.
let url = getPostMeta( 19, 'large_preview', true );
console.log( url );

// Retrieve all meta values.
// Always returns an array of values.
let ms = getPostMeta( 19, 'many_values' );
console.log( ms, ms[0] );

// Retrieve all meta data.
// Always returns an object with meta_key => meta_value pairs. I.e. { key => value, ... }
let ma = getPostMeta( 19 );
console.log( ma, ma.large_preview, ma.many_values[0] );

But if you really must combine the JSON data, you can do: (again, see demo on the same Fiddle)

// Here we modify the original `posts` object.
posts.map( p => {
    // Add all the post's meta data.
    p.meta = getPostMeta( p.ID );

    // Delete items you don't want..
    delete p.post_parent;
    delete p.menu_order;
    // delete ...;
});

console.log( JSON.stringify( posts[0].meta ) ); // posts[0].meta = object
console.log( posts[0].post_parent, posts[0].menu_order ); // both are undefined

And then if you want to copy-paste the new/merged JSON data:

JSON.stringify( posts );

But if you actually just want to do something with the post's meta, you can loop through the posts object and do the thing; e.g.:

// Here the original `posts` object is not modified, and that we don't
// (though you can) repeatedly call `getPostMeta()` for the same post.
posts.map( p => {
    // Get all the post's meta data.
    let meta = getPostMeta( p.ID );

    // Do something with `meta`.
    console.log( meta.large_preview );
});

console.log( JSON.stringify( posts[0].meta ) ); // posts[0].meta = undefined
console.log( posts[0].post_parent, posts[0].menu_order ); // both still defined

// posts[0].meta wouldn't be undefined if of course posts[0] had a `meta` item,
// which was set in/via WordPress...
Sally CJ
  • 15,362
  • 2
  • 16
  • 34
5

If you can do this in js, there's a pretty easy approach using Array#map. If you simplify your question, you're really asking how to add this meta data under each entry in posts, and get only the fields you want.

I'm assuming the posts.json is actually an array (e.g. [{"ID":"19"....).

// Load these server-side, fetch them remotely, copy-paste, etc.
// I'll require them here for simplicity
const posts = require('./posts.json');
const postsMeta = require('./postsMeta.json');

// Build a Map so we can quickly look up the metas by post_id
// Extract what we need by destructuring the args
const metaByPost = postsMeta.reduce((a, {
  post_id: id,
  meta_value: value,
}) => a.set(id, {
  value,
  /* anything else you want in here */,
}), new Map());

const mergedPosts = posts.map(post => ({
  // Spread in the post
  ...post,
  // Spread in the meta content
  ...metaByPost.get(post.ID),
  // Undefine the props we don't want
  post_parent: undefined,
}));

I don't love manually setting stuff to undefined -- I think it's nicer to explicitly say what props you're going to include, instead of loading everything and undefining certain props.

Josh from Qaribou
  • 6,776
  • 2
  • 23
  • 21
  • A little stuck here already. If i need to load these files server side, I am thinking to use meteor place them in the server folder and import them by path, or do I still use the `const posts` format I also I don't know what to put in the section `mergedPosts` it says Spread in the post, do I literally just put the value `post`... so on does this mean the `mergedPosts` variable going to be an array of the two combined json files? – Anders Kitson Apr 30 '18 at 22:52
  • @AndersKitson take a look at this for more info on spreads: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals – Josh from Qaribou May 01 '18 at 01:56
  • What that code's doing is taking the properties in post, the properties in the meta content for that post, and building a new object of them both put together. Object props always are set to their last one, so `post_parent: undefined` will set it to undefined if it comes last. – Josh from Qaribou May 01 '18 at 01:57
  • Ok so the `...` is the actual spread syntax? I assumed you put that in as a fill in the missing part. Also when you say load these server side? Can I do that in Node js? Or in a framework like meteor, or do you suggest another way of doing so? – Anders Kitson May 01 '18 at 02:09
  • I tried running this on glitch on the server side you can see it all here https://glitch.com/edit/#!/join/ca634d31-d3eb-4fe0-a8fb-cce34f4d862c I am getting a `Map is not defined error` – Anders Kitson May 03 '18 at 22:35
5

Try this snippet directly in the Chrome DevTools console:

(function(
  postsUrl='https://cdn.glitch.com/61300ea6-6cc4-4cb6-a62f-31adc62ea5cc%2Fposts.json?1525386749382',
  metaUrl='https://cdn.glitch.com/61300ea6-6cc4-4cb6-a62f-31adc62ea5cc%2Fpostmeta.json?1525386742630'
) {
  Promise.all([
    fetch(postsUrl).then(r => r.json()),
    fetch(metaUrl).then(r => r.json()),
  ]).then(([postsResponse, metaResponse]) => {
    // Inspected the actual JSON response to come up with the data structure
    const posts = postsResponse[2].data;
    const meta = metaResponse[2].data;
    const metaByPostId = meta.reduce((accum, el) => {
      accum[el.post_id] = el;
      return accum;
    }, {});
    const transformedPosts = posts.map(post => {
      const merged = {
        ...post,
        ...(metaByPostId[post.ID] || {}),
      };
      delete merged.post_parent;
      // delete any other fields not wanted in the result
      return merged;
    });
    console.log(transformedPosts);
  });
})();
  • replace URLs accordingly, I used the ones from Glitch example here
  • as commented, actual data is buried in response[2].data. Use Network tab / Parsed view to see structure
  • replace console.log with copy, if you want the result copied to clipboard, instead of logged to console
atmin
  • 1,231
  • 1
  • 8
  • 7
  • Hey thanks for the help, I am getting a `TypeError: meta is undefined` when running this locally. I tried running it on Glitch but I got an error `The page’s settings blocked the loading of a resource` Got any ideas. – Anders Kitson May 10 '18 at 02:10
  • That most probably means the request to `metaUrl` fails and then `const meta = metaResponse[2].data` fails. You can find out by observing Devtools / Network. Could be a browser extension, try Incognito session. I just tried on Chrome and it works for me. Alternatively, get the inner function only (`const posts = JSON.parse(''); const meta = JSON.parse(''); ........ console.log(...);`, note added 2x `JSON.parse`) and execute that, to skip network at all. – atmin May 11 '18 at 07:40
  • Ok that seemed to work, however I don't see the `meta_value` from the `postsMeta.json` file IE: the image image url form the `postsMeta` That is one of the main ones I need to be in the final `transformedPosts` The `transformedPosts` appears to be only one of the json files in specific it is the `posts.json` not the merge of the two. – Anders Kitson May 12 '18 at 21:36
  • oh, you're right, this is a bug, `metaByPostId[post.post_id]` should be `metaByPostId[post.ID]`, going to fix the answer – atmin May 14 '18 at 10:01
2

Blunt to the point for your question. We want to:

  • merge var a = {/*some json*/} into var b = {/*another json*/}
  • trim fields in var exclusions = ["post_parent","meta_key"]

Merge the JSONS

First, we need to populate a and b. Your JSONs are parsable into Javascript objects with JSON.parse():

let a = JSON.parse(/*JSON here*/);
let b = JSON.parse(/*JSON here*/);

Because how properties are defined in Javascript, if you define a property again, the second definition will overwrite the first. Your JSONS contain only strings as keys and strings as values, so a shallow copy will suffice. Object.assign() will copy all the properties (field and values) into the first argument and return the final Object. Therefore this will merge a into b, assuming they have different keys, else the values in b will overwrite values in a:

a = Object.assign(a,b);

Otherwise, if they are not disjoint, you have to define some policy on how to join, for example may prioritize one. Below, we keep the values in a instead:

a = Object.assign(b,a);

Since you mentionmed a for loop, the line below does the same as two code lines above and will also allow show you an example on how to write your own custom lambda expression:

Object.keys(a).forEach(k=>b[k]=b[k]?b[k]:a[k]);

Do not wish to touch a and b? Create a third object c.

let c = Object.assign({},a,b)

Lastly (wait until the trim step below is accomplished) JSON.stringify() will convert your merged object back into JSON.

Trim exclusions

Following the third example, we have c merged with all the fields.

First a little hack taken from here:

Object.filter = (obj, predicate) => Object.keys(obj)
    .filter( key => predicate(obj[key]))
    .reduce( (res, key) => (res[key] = obj[key], res), {} );

Now Objects, just like arrays have a filter prototype, having extended Object prototype. It is not really best practice since this will extend every Object but this function works quite well with respects to the semantics of Javascript and this example serves as an opportunity to keep elegant Javascript styles code:

c = Object.filter(c, key=> !exclusions.includes(key) );

Voit-lá, done.

As for defined Object.filter() it uses Array.filter() and Array.reduce() . Click for reference, for your convenience.

Attersson
  • 4,755
  • 1
  • 15
  • 29
  • inside `let a = JSON.parse(/*JSON here*/);` do we put in the path to the json File like `let a = JSON.parse(postmeta.json);` or then entire array? – Anders Kitson May 12 '18 at 21:45
  • the entire JSON as string, check here https://www.w3schools.com/js/js_json_parse.asp but you can open the file have the string. For example if you are using node you can use fs https://www.w3schools.com/nodejs/nodejs_filesystem.asp – Attersson May 12 '18 at 21:47
  • And so: `var a = JSON.parse(fs.readFileSync(filepath));` (with NodeJS) – Attersson May 12 '18 at 21:50
  • I am attempting your code here https://glitch.com/edit/#!/tricky-rail I get a `Unexpected token u in JSON at position 0` – Anders Kitson May 12 '18 at 22:03
  • I mean, every single line, within the array, needs to be treated separately. First get the entire file string with `var strA =fs.readFileSync(filepath)` then strip the [ at the start, ] at the end and then with strA.split(",") you obtain the array of JSON strings. – Attersson May 12 '18 at 22:13
  • I am now getting `exclusions is not defined`? – Anders Kitson May 12 '18 at 22:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/170925/discussion-between-attersson-and-anders-kitson). – Attersson May 12 '18 at 22:20