Code Monkey home page Code Monkey logo

jsonparse's Introduction

JSONParse

Salesforce Apex JSON parser to make it easier to extract information from nested JSON structures.

If you're sick of writing code like this:

Map<String, Object> root = (Map<String, Object>)JSON.deserializeUntyped(someJSON);
Map<String, Object> menu = (Map<String, Object>)root.get('menu');
Map<String, Object> popup = (Map<String, Object>)menu.get('popup');
List<Object> menuitem = (List<Object>)menu.get('menuitem');
Map<String, Object> secondItem = (Map<String, Object>)menuitem.get(1);
String thingIActuallyWanted = String.valueOf(secondItem.get('name'));

...then this parser is for you! Voila!

new JSONParse(someJSON).get('menu.popup.menuitem.[1].name').getStringValue();

Do I have your attention? Great! Now let's go a little deeper.

Concepts

The idea of JSONParse is that a JSON payload is treated as a tree, with each node in the tree wrapped in an instance of JSONParse. So you start with a JSONParse instance at the root, and as you drill deeper into the nested data structure you are revealing yet more JSONParse instances.

At any time you can use your current JSONParse instance to get one of two collection types (Maps/Lists), or raw data primitives, depending on what this particular JSONParse node is wrapping (an object, an array, or a primitive).

A little fuzzy? That's OK, let's look at some examples.

Usage

Let's start with a simple example. Say we have the following JSON structure:

{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}

We always start by instantiating JSONParse with a String value that holds some JSON:

JSONParse root = new JSONParse(someJSONData);

If we wanted to get to the value property inside menu, we would do this:

root.get('menu.value').getStringValue(); // "File"

But what is actually happening here? Let's be a little more verbose:

JSONParse childNode = root.get('menu.value');
childNode.getStringValue(); // "File"

The get() method is our key workhorse method in JSONParse. It allows us to drill into the tree structure and always returns an instance of JSONParse.

For additional clarity:

System.debug(root.toStringPretty());
{
  "menu" : {
    "popup" : {
      "menuitem" : [ {
        "onclick" : "CreateNewDoc()",
        "value" : "New"
      }, {
        "onclick" : "OpenDoc()",
        "value" : "Open"
      }, {
        "onclick" : "CloseDoc()",
        "value" : "Close"
      } ]
    },
    "value" : "File",
    "id" : "file"
  }
}
System.debug(root.get('menu.popup').toStringPretty());
{
  "menuitem" : [ {
    "onclick" : "CreateNewDoc()",
    "value" : "New"
  }, {
    "onclick" : "OpenDoc()",
    "value" : "Open"
  }, {
    "onclick" : "CloseDoc()",
    "value" : "Close"
  } ]
}
System.debug(root.get('menu.popup.menuitem').toStringPretty());
[ {
  "onclick" : "CreateNewDoc()",
  "value" : "New"
}, {
  "onclick" : "OpenDoc()",
  "value" : "Open"
}, {
  "onclick" : "CloseDoc()",
  "value" : "Close"
} ]
System.debug(root.get('menu.popup.menuitem.[0]').toStringPretty());
{
  "onclick" : "CreateNewDoc()",
  "value" : "New"
}

You can see as we drill deeper and deeper into the data structure, we get smaller and smaller slices of the tree back.

Don't be misled by these examples that all start from root. You can just as easily drill partway down, do some stuff, then keep going. You can even do things like this:

root.get('menu.popup').get('menuitem').get('[2].onclick').getStringValue();

What can I pass to get()?

public JSONParse get(String path) {}

The syntax for what you pass to the get() method is simple. You are passing a series of tokens separated by periods. A token is either:

  1. An array token
  2. A key token

Array tokens look like this: [2]

They are used to choose a specific item in an array.

The other token type, key tokens, are just simple strings that are going to be used to match on a JSON object property name. This matching is case sensitive.

You can mix and match these two token types to your heart's content. Just remember you are following your JSON data structure, so your get() path that you send should match it!

Tokens are always separated by a period. Here's a common mistake (don't do this):

// NOT VALID SYNTAX
root.get('menu.popup.menuitem[0]');

// VALID SYNTAX
root.get('menu.popup.menuitem.[0]');

Working with Collections

If you'd like to work with your collection nodes (object = Map, array = List), there are two methods on JSONParse:

public Map<String, JSONParse> asMap() {}
public List<JSONParse> asList() {}

So, to build on our previous examples, you could do something like this:

for(JSONParse node : root.get('menu.popup.menuitem').asList()) {

    System.debug('Onclick: ' + node.get('onclick').toStringValue());
}

Or this:

Map<String, JSONParse> menuProperties = root.get('menu').asMap();

System.debug(menuProperties.keySet()); // (id, value, popup)

for(String key : menuProperties.keySet()) {

  JSONParse node = menuProperties.get(key);
}

You can of course nest and repeat these patterns, to drill into arbitrarily complex data structures.

Dynamic Inspection

There's some discovery built into the parser so you can explore the JSON structure without knowing the shape in advance.

To do so, simply combine the two collection methods you just saw with these utility methods:

public Boolean isObject() {}
public Boolean isArray() {}

These two methods peek under the covers at the wrapped data and give you some information about what's inside. Here's an arbitrary example, where I use recursion to perform a dynamic inspection of the entire JSON tree. Obviously this is contrived but it should give you an idea of what's possible!

You can copy this entire snippet into Anonymous Apex and run it yourself.

Anonymous Apex Snippet

public void explore(JSONParse node, Integer depth) {

    if(!(node.isObject() || node.isArray())) {
        System.debug( '*'.repeat(depth) + node.getValue());
    }
	
    if(node.isObject()) {
        for(String key : node.asMap().keySet()) {
            explore(node.get(key), depth + 1);
        }
    }
	
    if(node.isArray()) {
        for(JSONParse item : node.asList()) {
            explore(item, depth + 1);
        }
    }
}

JSONParse root = new JSONParse('{"menu":{"id":"file","value":"File","popup":{"menuitem":[{"value":"New","onclick":"CreateNewDoc()"},{"value":"Open","onclick":"OpenDoc()"},{"value":"Close","onclick":"CloseDoc()"}]}}}');

explore(root, 0);

Result

13:37:23.34 (39921697)|USER_DEBUG|[3]|DEBUG|**file
13:37:23.34 (40158891)|USER_DEBUG|[3]|DEBUG|**File
13:37:23.34 (40964502)|USER_DEBUG|[3]|DEBUG|*****New
13:37:23.34 (41091905)|USER_DEBUG|[3]|DEBUG|*****CreateNewDoc()
13:37:23.34 (41310876)|USER_DEBUG|[3]|DEBUG|*****Open
13:37:23.34 (41477025)|USER_DEBUG|[3]|DEBUG|*****OpenDoc()
13:37:23.34 (41820815)|USER_DEBUG|[3]|DEBUG|*****Close
13:37:23.34 (41952080)|USER_DEBUG|[3]|DEBUG|*****CloseDoc()

Primitive Value Extraction

A parser is no good if you can't get to the juicy stuff it's wrapped around!

There are a number of methods to pull primitive values out of JSONParse nodes. In general we followed the conventions in the native JSONParser class, except in a few places where we supported a broader / more flexible set of behaviors. For example, you can build Date or Time instances from Long values.

public Blob getBlobValue() {}

public Boolean getBooleanValue() {}

public Datetime getDatetimeValue() {}

public Date getDateValue() {}

public Decimal getDecimalValue() {}

public Double getDoubleValue() {}

public Id getIdValue() {}

public Integer getIntegerValue() {}

public Long getLongValue() {}

public String getStringValue() {}

public Time getTimeValue() {}

public Object getValue() {}

Disclaimers

This helper class only reads JSON, it does not write it. For writing, may we suggest using the native and perfectly serviceable JSON.serialize()!

Deploy (SFDX)

Covert with SFDX; This creates a folder called deploy

sfdx force:source:convert -r force-app -d deploy

Now you can deploy from the resulting deploy directory

sfdx force:mdapi:deploy -d deploy -w -1  --verbose

๐Ÿ“Œ Above deploys to the default org set without running tests

  • To deploy else where, add -u [email protected] or -u alias
  • To run tests, add -l RunSpecifiedTests -r JSONParseTests

Results should more or less mirror below

Using specified username [[email protected]]

Job ID | 0Af1h00000VfFI3CAN

MDAPI PROGRESS | โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ | 2/2 Components

TYPE       FILE                               NAME            ID
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
           deploy/package.xml                 package.xml
ApexClass  deploy/classes/JSONParse.cls       JSONParse       01p1h000005B9QYAA0
ApexClass  deploy/classes/JSONParseTests.cls  JSONParseTests  01p1h000005B9QZAA0

jsonparse's People

Contributors

grekker avatar jsmithdev avatar

Stargazers

Reid Efford avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.