Json to valuetree parser


#1

I recently needed to parse some JSON and decided I wanted it as a valuetree - so here is a quick and easy JSON to ValueTree class.

This class relies on the jsoncpp libraries (probably the most standard json reader libs available - very easy to use). I included the source rather than compiling and linking since I didn’t want to mess with the python testing. Under MIT license (public domain).

http://jsoncpp.sourceforge.net/
http://sourceforge.net/projects/jsoncpp/files/

Use the class like so:


#include "JsonToJuce.h"

JsonToJuceReader* myReader = new JsonToJuceReader();
myReader->ReadJsonToValueTree(jsonString, myValueTree);
delete myReader;

After that, the Json will be represented in myValueTree. Comments (in the JSon) are ignored, incidentally.

You’ll also notice that you can call … PrintJSONTree(rootJsonValue, 0); to print the entire tree to the console.

“JsonToJuce.h” :


//
//  JsonToJuce.h
//  Juce Demo
//
//  Created by Aaron Leese on 5/17/11.
//  Copyright 2011 StageCraft Software. All rights reserved.
//
 
#include "juceHeader.h"

#include <utility>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <iostream>
#include <stdexcept>

// following includes from jsontocpp library ...
// http://sourceforge.net/projects/jsoncpp/

#include "reader.h"
#include "value.h"
#include "features.h"

/////////////////


class JsonToJuceReader : public Thread
{
   
public:
    
    JsonToJuceReader() : Thread("json reader thread") 
    {
       //    
    }
    
    void ReadJsonToValueTree(String jsonStream, juce::ValueTree * juceTreeNode)
    {
        // only one at a time ....
        if (isThreadRunning()) return; 
            
        treeToPopulate = juceTreeNode;
        
        // create the root JSON node
        Json::Reader* myJsonReader = new Json::Reader();
        rootOfJsonTree = new Json::Value();
        
        // parse the string to the jsonCPP object
        std::string stdJsonString(jsonStream.toUTF8()); 
        
        bool success = myJsonReader->parse(stdJsonString, *rootOfJsonTree);
      
        if (success) 
        {
            startThread(2);
        }
    }
    
private:
    
    ValueTree* treeToPopulate;
    Json::Value* rootOfJsonTree;
    
    // Printing ...
    void PrintJSONValue( Json::Value & val ) {
        if( val.isString() ) {
            printf( "string(%s)", val.asString().c_str() ); 
        } else if( val.isBool() ) {
            printf( "bool(%d)", val.asBool() ); 
        } else if( val.isInt() ) {
            printf( "int(%d)", val.asInt() ); 
        } else if( val.isUInt() ) {
            printf( "uint(%u)", val.asUInt() ); 
        } else if( val.isDouble() ) {
            printf( "double(%f)", val.asDouble() ); 
        }
        else 
        {
            printf( "unknown type=[%d]", val.type() ); 
        }
        
        
    }
    bool PrintJSONTree( Json::Value & root, unsigned short depth /* = 0 */)  {
        depth += 1;
        printf( " {type=[%d], size=%d}", root.type(), root.size() ); 
        
        if( root.size() > 0 ) {
            printf("\n");
            for( Json::ValueIterator itr = root.begin() ; itr != root.end() ; itr++ ) {
                // Print depth. 
                for( int tab = 0 ; tab < depth; tab++) {
                    printf("-"); 
                }
                printf(" child(");
                
                Json::Value temp = itr.key();
                PrintJSONValue(temp);
                
                printf(") -");
                PrintJSONTree( *itr, depth); 
            }
            return true;
        } else {
            printf(" prop -> ");
            PrintJSONValue(root);
            printf( "\n" ); 
        }
        return true;
    }
    
    // Translating to ValueTree
    void AddChildToTree( Json::Value & val, juce::ValueTree & juceTreeNode) {
        
        if( val.isString() ) {
            
            String newChildName = String(val.asString().c_str()).retainCharacters("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_");
            juceTreeNode.addChild(ValueTree(newChildName), -1 ,0);
            
        } else if( val.isBool() ) {
            juceTreeNode.addChild(ValueTree(String(val.asBool())), -1 ,0);
        } else if( val.isInt() ) {
            juceTreeNode.addChild(ValueTree(String(val.asInt())), -1 ,0);
        } else if( val.isUInt() ) { 
            juceTreeNode.addChild(ValueTree(String(val.asUInt())), -1 ,0);
        } else if( val.isDouble() ) { 
            juceTreeNode.addChild(ValueTree(String(val.asDouble())), -1 ,0);
        } else if( val.isIntegral() ) { 
            juceTreeNode.addChild(ValueTree(String(val.asDouble())), -1 ,0);
        } else if( val.isNumeric() ) { 
            juceTreeNode.addChild(ValueTree(String(val.asDouble())), -1 ,0);
        } else if( val.isArray()) { 
            juceTreeNode.addChild(ValueTree("Array"), -1 ,0);
        }
        else 
        {
            juceTreeNode.addChild(ValueTree("unknown"), -1 ,0);
        }
        
        
    }
    void AddPropertyToTree( Json::Value & val, juce::ValueTree & juceTreeNode) {
        
        if( val.isString() ) {
            
           // String newPropName = String(val.asString().c_str()).retainCharacters("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_");
            juceTreeNode.setProperty("value", val.asString().c_str(), 0); 
            
        } else if( val.isBool() ) {
            juceTreeNode.setProperty("value", val.asBool(), 0); 
        } else if( val.isInt() ) {
            juceTreeNode.setProperty("value", val.asInt(), 0);  
        } else if( val.isUInt() ) {
            juceTreeNode.setProperty("value", int(val.asUInt()), 0);  
        } else if( val.isDouble() ) {
            juceTreeNode.setProperty("value", double(val.asDouble()), 0); 
        }
        else 
        {
            printf( "unknown type=[%d]", val.type() ); 
        }
        
    }
    
    bool Read(Json::Value & root, juce::ValueTree & juceTreeNode, unsigned short depth /* = 0 */) 
    {
        depth += 1;
        
        if( root.size() > 0 ) {
            
            for( Json::ValueIterator itr = root.begin() ; itr != root.end() ; itr++ ) {
                
                Json::Value temp = itr.key();
                // this should be added as a child ... 
                AddChildToTree(temp, juceTreeNode);
                
                // dive down a level ...
                ValueTree newChild = juceTreeNode.getChild(juceTreeNode.getNumChildren()-1); // this should get us the one just added
                Read( *itr, newChild, depth); 
            }
            return true;
        } else {
            
            // this should be added as a property ... since there are no children
            AddPropertyToTree(root, juceTreeNode);
            
        }
        return true;
    }

    void run()
    {
        Read(*rootOfJsonTree, *treeToPopulate, 0); 
        
        
        DBG("done - write to file");
        // save ....
        
        File* testWrite = new File(File::getSpecialLocation(File::userDesktopDirectory).getChildFile("testttt"));
        FileOutputStream* testStream = new FileOutputStream(*testWrite);
        
        // binary 
        // ValueTree::writeToStream(	OutputStream & 	output )	
        //  myTrackInformation.writeToStream(testStream);
        
        XmlElement* temp = treeToPopulate->createXml();
        temp->writeToFile(*testWrite, String::empty); 
        delete temp;
        
        delete testStream;
        delete testWrite;
          
    }
    
};



#2

I was about to write a JSON parser myself recently (and will certainly add one to the library in the near future). The thing that stopped me was that neither the ValueTree or var classes exactly mirror the format of JSON, so I need to decide how best to tweak them to represent arrays.


#3

I’ve been doing a huge amount of JSON-ish things in the last two years, and I’ve been pushing it on poor overworked, spavined Jules so let me make you a recommendation or two (including a really good library toward the bottom of this…)

First, I think everyone should dump XML forever and be doing JSON or JSON-like structures (I specifically use Google protocol buffers for my data language, but that’s got a similar structure, and my external format is JSON…) XML is hard to read, even harder to successfully edit, and the DTD/Schema etc. is just a Bad Idea - lots of work for, at the end, no real gain at all.

So JSON!

But if you’re going to do JSON anyway, then do YAML!

YAML is a strict superset of JSON - which means that you can have a YAML parser and set it to write JSON and no one will know that you aren’t plain ol’ JSON. (Some rarer features of YAML, mainly references between nodes, are lost when you do that, but you still get the data, it’s just copied into multiple places, so the data really is round-trip).

But why use it over JSON then?

Well, it’s more compact than JSON, it’s more robust against errors(*), and it’s easier to read - and it also offers additional functionality like end-of-record and end-of-stream, references and such. It’s very readable, sparse and minimal, and if you show it to non-computer people they like it better than JSON and far better than XML.

tl; dr: it looks really cool.

And it cut and pastes very nicely into an email, and in such a way that it’s incredibly easily recognized with e.g. a regular expression (as opposed to e.g. JSON, where you can’t just match on {).

So my new apps all write in YAML, with a default to let you switch back to JSON for interchange. Let me paste some data files right from my code - the names like “type” and “value” mean nothing to YAML, they’re just to do with my specific code…[code]type: rec.gui.audio.LoopPointList
value: {loop_point: [{time: 787.665, selected: true}, {time: 1011.65, selected: true}]}

type: rec.gui.audio.LoopPointList
value:
loop_point:
- {time: 347.17, selected: true}
- {time: 421.83, selected: true}
- {time: 739.136, selected: true}
- {time: 787.665, selected: true}
- {time: 1011.65, selected: true}
—[/code]

I’ve personally been using yaml-cpp - it’s been very reliable, good support (my one minor interface request was accepted and done within a week) - and again, you can use that library and easily set it to only write (and of course read) JSON - for now…


(* - how does that work? Well, there are a lot of different ways to measure robustness against errors - my personal one is to simply imagine that you take one character at random in the stream and change it randomly - what is the magnitude of the damage? There are four possible outcomes from the change - the first is that no error occurs (e.g. a space changes to a tab), the second is that the file is rendered unreadable (e.g. if you delete a > in your XML), the third is that some data is simply lost (e.g. if the character becomes a # in YAML or JSON), the fourth is that some data is corrupted.

(In YAML you can nearly always recover from an error by the next carriage return - JSON makes failure more likely (lose a } or a " and you’re out of luck forever) and XML even worse…)


#4

Have you guys seen the “UniversalContainer” (http://www.greatpanic.com/code.html). I just recently started using it in some telecom network code that I have. It’s legacy code that needed to be “RESTified” :slight_smile:

This container is really neat, especially the indexing aspects… you can use dotted notation to access inner elements:

Container[1.name] = "Renny"
Container[1.job] = "Chief Bottlewasher"
Container[1.salary] = 30000;

You can mix & match types…