Boosts JSON解析器在MacOS上转义引号但在Linux上没有

时间:2015-05-19 12:17:38

标签: c++ json linux boost clang

我有一个C ++程序在服务器上运行,返回JSON格式的数据,用boost序列化。在某些情况下,JSON数据包的一个值部分是另一个序列化为字符串的JSON数据包。在我的开发机器上运行XCode,程序返回带有转义引号的值数据,在Linux系统上编译而没有。有没有办法强制推动linux做这个或一个好的解决方法? (一般来说,我们已经修复了缺失值引号的错误)。

Beneath就是第一行的例子:

local:
"stations": "{\n \"area CHALLENGE_FUTURE\": \"{\\n \\\"areaname CHALLENGE_FUTURE\\\": [...]"
online:
"stations": "{\n "area CHALLENGE_FUTURE": "{\\n "areaname CHALLENGE_FUTURE": [...]"

Boost版本是1.57,编译器(本地Xcode和在线Linux)是clang-linux- 3.5。 (见编辑)

以下是简化代码:

// ----- STL
#include <iostream>
#include <string>
#include <map>

// ------ boost
#include <boost/asio.hpp>
#include <boost/foreach.hpp>


//formatter_base.hpp
//------------------------------------------------------------------------------
class formatter_base
{
protected:
    std::map<std::string, std::string> datas;

public:
    virtual ~formatter_base() {}
    void setvalue(std::string key, std::string value)
    {
        datas[key] = value;
    }

    std::string getvalue(std::string key)
    {
        return datas[key];
    }

    bool containsKey(std::string key)
    {
        return (datas.find(key) != datas.end());
    }

    virtual void deserialize(char *data, const std::size_t size) = 0;
    virtual std::string serialize(std::vector<std::string> keys) = 0;
};

//json_formatter.hpp
class json_formatter : public formatter_base
{
public:
    virtual void deserialize(char *data, const std::size_t size);
    virtual std::string serialize(std::vector<std::string> keys);
};


//datapacket.hpp
//------------------------------------------------------------------------------
class server;
extern server *tcp_server;

class datapacket
{
    static const char id[4];

public:
    enum DataFormat { BINARY = 0, JSON, XML };

    std::string ip;
    bool useHeader;

    datapacket() : useHeader(false), packet_data(NULL) {}
    datapacket(DataFormat format);
    std::vector<char> process(char *data, std::size_t size, std::string ip);

    std::string getvalue(std::string key)
    {
        return packet_data->getvalue(key);
    }

    void setvalue(std::string key, std::string value)
    {
        packet_data->setvalue(key, value);
    }

    bool containsKey(std::string key)
    {
        return packet_data->containsKey(key);
    }

    std::vector<char> serialize();
    std::string       toString();

private:
    bool deserialize(char *data, std::size_t size);
    std::string serialize_data(std::vector<std::string> keys);

    formatter_base *packet_data;
};

//datapacket.cpp
//------------------------------------------------------------------------------
#include <boost/iostreams/stream.hpp>
#include <boost/foreach.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <string.h>

datapacket::datapacket(DataFormat format)
: useHeader(false)
{
    if(format == JSON)
    {
        packet_data = new json_formatter();
    }
    else
    {
        packet_data = NULL;
    }
}


std::vector<char> datapacket::process(
                                      char *data, std::size_t size, std::string ip)
{
    //std::cout << "datapacket::process" << std::endl;

    this->ip = ip;
    std::vector<char> ret;

    if (!deserialize(data, size))
        return ret;

    std::vector<std::string> keys;
    std::string result;

    /*extern void process(datapacket& gamedata);
     process(*this);*/

    ret.push_back('a');
    ret.push_back('c');
    ret.push_back('k');

    return ret;
}

bool datapacket::deserialize(char *data, std::size_t size)
{
    packet_data = new json_formatter();
    packet_data->deserialize(data, size);
    return true;
}

std::string datapacket::serialize_data(std::vector<std::string> keys)
{
    return packet_data->serialize(keys);
}

std::vector<char> datapacket::serialize()
{
    std::vector<std::string> keys;
    std::string str = serialize_data(keys);
    std::vector<char> ret;
    ret.assign(str.begin(), str.end());

    return ret;
}

std::string datapacket::toString()
{
    std::vector<std::string> keys;
    std::string str = serialize_data(keys);

    return str;
}


//json_formatter.cpp
//------------------------------------------------------------------------------
using namespace boost::property_tree;

void json_formatter::deserialize(char *data, const std::size_t size)
{
    std::stringstream ss;
    ss.write(data, size);

    //  std::cout << "ss: " << ss.str() << std::endl;

    ptree pt;
    read_json(ss, pt);

    BOOST_FOREACH(ptree::value_type &v, pt)
    {
        //log all received data
        //std::cout << v.first.data() << ": " << v.second.data() << std::endl;

        datas[v.first.data()] = v.second.data();
    }
}

///-workaround 1.57 json
template <typename T>
struct my_id_translator
{
    typedef T internal_type;
    typedef T external_type;

    boost::optional<T> get_value(const T &v) { return  v.substr(1, v.size() - 2) ; }
    boost::optional<T> put_value(const T &v) { return '"' + v + '"'; }
};
///
std::string json_formatter::serialize(std::vector<std::string> keys)
{


    ptree pt;

    if(keys.empty())
    {
        typedef std::map<std::string, std::string> mapType;
        BOOST_FOREACH(const mapType::value_type& myPair, datas)
        {
            //workaround for wrong formatted string
            if((BOOST_VERSION == 105700) && (BOOST_OS_LINUX))
            {
                //1.57
                pt.put(myPair.first, myPair.second, my_id_translator<std::string>());
            }
            else
            {
                //1.54
                pt.put(myPair.first, myPair.second);
            }
            //std::cout << myPair.first << ": " << myPair.second << std::endl;
        }
    }
    else
    {
        BOOST_FOREACH(std::string key, keys)
        {
            //workaround for wrong formatted string
            if(BOOST_VERSION == 105700)
            {
#if BOOST_OS_LINUX
                pt.put(key, "\"" + datas[key] + "\"", my_id_translator<std::string>());
#else
                pt.put(key, datas[key], my_id_translator<std::string>());
#endif
            }
            else
            {
                pt.put(key, datas[key]);
            }
            //      std::cout << key << ": " << datas[key] << std::endl;
        }
    }

    std::stringstream ss;
    write_json(ss, pt);
    std::string str = ss.str();

    // WORKAROUND
    // Replace all escaped backslashes
    // This was because some clients couldn't interpret "command\\/read"
    std::string oldStr = "\\/";
    std::string newStr = "/";

    std::size_t pos = 0;
    while((pos = str.find(oldStr)) != std::string::npos){
        str = str.replace(pos, oldStr.length(), newStr);
        pos += newStr.length();
    }
    // /WORKAROUND

    //std::cout << "Serialize message:" << std::endl;
    //std::cout << str << std::endl;
    return str;
}


//main.cpp
//------------------------------------------------------------------------------
class dataClass
{
public:
    dataClass() {}
    std::string name;
};

class innerDataClass
{
public:
    innerDataClass() {}
    std::string name;
    int         score;
    std::string baseClassName;
};

using boost::asio::ip::tcp;


namespace stdpatch
{
    template <typename T> std::string to_string(const T& n)
    {
        std::ostringstream stm;
        stm << n;
        return stm.str();
    }
}

std::map<std::string, dataClass>        listDC;
std::map<std::string, innerDataClass>   listIDC;

void Init()
{
    //Some initial values
    dataClass d1; d1.name = "dataClass1"; listDC["d1"] = d1;
    dataClass d2; d2.name = "dataClass2"; listDC["d2"] = d2;

    innerDataClass i1; i1.name = "innerClass1"; i1.baseClassName = "dataClass1";
    i1.score = 5; listIDC["i1"] = i1;
    innerDataClass i2; i2.name = "innerClass2"; i2.baseClassName = "dataClass1";
    i1.score = 21; listIDC["i2"] = i2;
    innerDataClass i3; i3.name = "innerClass3"; i1.baseClassName = "dataClass2";
    i1.score = 1; listIDC["i3"] = i3;
}

//returns JSON
datapacket GetJSON()
{
    std::pair<std::string, dataClass>       baseClassPair;
    std::pair<std::string, innerDataClass>  innerClassPair;

    datapacket baseClasses (datapacket::JSON);
    baseClasses.setvalue("comment", "this holds all the base classes");
    BOOST_FOREACH(baseClassPair, listDC)
    {
        datapacket baseClassData (datapacket::JSON);
        baseClassData.setvalue("dataName", baseClassPair.first);
        BOOST_FOREACH(innerClassPair, listIDC)
        {
            if (innerClassPair.second.baseClassName == baseClassPair.second.name)
            {
                datapacket innerClassData (datapacket::JSON);
                innerClassData.setvalue(
                                        "name", innerClassPair.second.name);
                innerClassData.setvalue(
                                        "score", stdpatch::to_string(innerClassPair.second.score));

                baseClassData.setvalue(
                                       "inner " + innerClassPair.first, innerClassData.toString());
            }
        }
        baseClasses.setvalue("base " + baseClassPair.first, baseClassData.toString());
    }

    datapacket packet (datapacket::JSON);
    packet.setvalue("comment", "this is the base-packet");
    packet.setvalue("baseClasses", baseClasses.toString());
    return packet;
}


int main(int argc, char* argv[])
{
    Init();
    datapacket packet (datapacket::JSON);
    packet = GetJSON();

    std::cout << std::endl << std::endl
    << "------------- RESULT --------------"
    << std::endl << std::endl;

    std::cout << packet.toString() << std::endl;

    return 0;
}

预期输出应为:

------------- RESULT --------------

baseClasses: {
    "base d1": "{\n    \"dataName\": \"d1\",\n    \"inner i1\": \"{\\n    \\\"gameID\\\": \\\"5\\\",\\n    \\\"name\\\": \\\"innerClass1\\\"\\n}\\n\",\n    \"inner i2\": \"{\\n    \\\"gameID\\\": \\\"1989860191\\\",\\n    \\\"name\\\": \\\"innerClass2\\\"\\n}\\n\"\n}\n",
    "base d2": "{\n    \"dataName\": \"d2\"\n}\n",
    "comment": "this holds all the base classes"
}

comment: this is the base-packet
{
    "baseClasses": "{\n    \"base d1\": \"{\\n    \\\"dataName\\\": \\\"d1\\\",\\n    \\\"inner i1\\\": \\\"{\\\\n    \\\\\\\"gameID\\\\\\\": \\\\\\\"5\\\\\\\",\\\\n    \\\\\\\"name\\\\\\\": \\\\\\\"innerClass1\\\\\\\"\\\\n}\\\\n\\\",\\n    \\\"inner i2\\\": \\\"{\\\\n    \\\\\\\"gameID\\\\\\\": \\\\\\\"1989860191\\\\\\\",\\\\n    \\\\\\\"name\\\\\\\": \\\\\\\"innerClass2\\\\\\\"\\\\n}\\\\n\\\"\\n}\\n\",\n    \"base d2\": \"{\\n    \\\"dataName\\\": \\\"d2\\\"\\n}\\n\",\n    \"comment\": \"this holds all the base classes\"\n}\n",
    "comment": "this is the base-packet"
}

但引号在我的服务器案例中没有转义:

comment: this is the base-packet
{
    "baseClasses": "{\n    "base d1": "{\\n    "dataName": "d1",\\n    "inner i1": "{\\\\n    "gameID": "5",\\\\n    "name": "innerClass1"\\\\n}\\\\n",\\n    "inner i2": "{\\\\n    "gameID": "1989860191",\\\\n    "name": "innerClass2"\\\\n}\\\\n"\\n}\\n",\n    "base d2": "{\\n    \\\"dataName": "d2"\\n}\\n",\n    "comment": "this holds all the base classes"\n}\n",
    "comment": "this is the base-packet"
}

为了测试问题是在write_json还是传输中我做了一个简单的JSON包:

datapacket testData(datapacket::JSON);
testData.setvalue("text", "\"world\"");
testData.setvalue("inner1", testData.toString());
testData.setvalue("inner2", testData.toString());

结果如下:

XCode:
{
    "inner1": "{\n    \"text\": \"\\\"world\\\"\"\n}\n",
    "inner2": "{\n    \"inner1\": \"{\\n    \\\"text\\\": \\\"\\\\\\\"world\\\\\\\"\\\"\\n}\\n\",\n    \"text\": \"\\\"world\\\"\"\n}\n",
    "text": "\"world\""
}

Server:
{  
    "inner1": "{\n    "text": ""world""\n}\n"
    "inner2": "{\n    "text": ""world"",\n    "inner1": "{\\n    "text": ""world""\\n}\\n"\n}\n",
    "text": ""world""
}

这应该表明问题在write_json

2 个答案:

答案 0 :(得分:1)

在我的linux盒子上,我测试过这不是问题:

<强> Live On Coliru

#include<boost/property_tree/json_parser.hpp>
#include<iostream>

int main(){
    using namespace boost::property_tree;

    ptree inner;
    inner.put("area CHALLENGE_FUTURE.areaname CHALLENGE_FUTURE", "something");

    std::ostringstream inner_json;
    write_json(inner_json, inner);

    ptree outer;
    outer.put("stations", inner_json.str());

    write_json(std::cout, outer);
}

打印

{
    "stations": "{\n    \"area CHALLENGE_FUTURE\": {\n        \"areaname CHALLENGE_FUTURE\": \"something\"\n    }\n}\n"
}

如果你问我,打印的确切内容。

如果你想要&#39;内心&#39; JSON要转义,为什么不让它成为同一棵树的一部分呢?

<强> Live On Coliru

#include<boost/property_tree/json_parser.hpp>
#include<iostream>

int main(){
    using namespace boost::property_tree;

    ptree outer;
    auto& inner = outer.add_child("stations", {});
    inner.put("area CHALLENGE_FUTURE.areaname CHALLENGE_FUTURE", "something");

    write_json(std::cout, outer);
}

打印

{
    "stations": {
        "area CHALLENGE_FUTURE": {
            "areaname CHALLENGE_FUTURE": "something"
        }
    }
}

答案 1 :(得分:0)

这可能是有史以来最愚蠢的错误:经过数周追踪错误后,我们发现我们用来在我们机器上构建项目的linux编译shell脚本手动改变了JSON解析行为。 如果我们从中学到了一些东西,那就是:永远不要相信你从互联网上复制过的东西。