Skip to content

Commit

Permalink
move code from include/gemmi/json.hpp to src/json.cpp
Browse files Browse the repository at this point in the history
  • Loading branch information
wojdyr committed Jan 22, 2024
1 parent 16044e3 commit 199c800
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 108 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ set_target_properties(gemmi_headers PROPERTIES EXPORT_NAME headers)

add_library(gemmi_cpp
src/align.cpp src/assembly.cpp src/calculate.cpp src/crd.cpp
src/ddl.cpp src/eig3.cpp src/gz.cpp
src/ddl.cpp src/eig3.cpp src/gz.cpp src/json.cpp
src/mmcif.cpp src/mmread_gz.cpp src/mtz.cpp src/mtz2cif.cpp
src/polyheur.cpp src/read_cif.cpp src/resinfo.cpp
src/riding_h.cpp src/sprintf.cpp src/to_mmcif.cpp
Expand Down
110 changes: 3 additions & 107 deletions include/gemmi/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,121 +4,17 @@

#ifndef GEMMI_JSON_HPP_
#define GEMMI_JSON_HPP_
#include <algorithm> // for move
#include <cstdio> // for FILE
#include <memory> // for unique_ptr
#include <string>
#include <vector>

#define SAJSON_UNSORTED_OBJECT_KEYS
#define SAJSON_NUMBERS_AS_STRINGS
#include "third_party/sajson.h"

#include <utility> // for forward
#include "cifdoc.hpp" // for Document, etc
#include "fail.hpp" // for fail, sys_fail
#include "fileutil.hpp" // for read_file_into_buffer

namespace gemmi {
namespace cif {
using std::size_t;

inline std::string json_type_as_string(sajson::type t) {
switch (t) {
case sajson::TYPE_INTEGER: return "<integer>";
case sajson::TYPE_DOUBLE: return "<double>";
case sajson::TYPE_NULL: return "<null>";
case sajson::TYPE_FALSE: return "<false>";
case sajson::TYPE_TRUE: return "<true>";
case sajson::TYPE_STRING: return "<string>";
case sajson::TYPE_ARRAY: return "<array>";
case sajson::TYPE_OBJECT: return "<object>";
default: return "<unknown type>";
}
}

inline std::string as_cif_value(const sajson::value& val) {
switch (val.get_type()) {
case sajson::TYPE_DOUBLE:
return val.as_string();
case sajson::TYPE_NULL:
return "?";
// mmJSON files from PDBj (this format has no spec) have special support
// for boolean YES|NO, which is used only in category _em_specimen.
// IMO it's a bad idea, but we must handle it if we want to read mmJSON.
case sajson::TYPE_FALSE:
return "NO"; // "." in CIF-JSON
case sajson::TYPE_TRUE:
return "YES";
case sajson::TYPE_STRING:
return quote(val.as_string());
default:
fail("Unexpected ", json_type_as_string(val.get_type()), " in JSON.");
return "";
}
}

inline void fill_document_from_sajson(Document& d, const sajson::document& s) {
// assuming mmJSON here, we'll add handling of CIF-JSON later on
sajson::value root = s.get_root();
if (root.get_type() != sajson::TYPE_OBJECT || root.get_length() != 1)
fail("not mmJSON");
std::string block_name = root.get_object_key(0).as_string();
if (!starts_with(block_name, "data_"))
fail("not mmJSON - top level key should start with data_\n"
"(if you use gemmi-cif2json to write JSON, use -m for mmJSON)");
d.blocks.emplace_back(block_name.substr(5));
std::vector<Item>& items = d.blocks[0].items;
sajson::value top = root.get_object_value(0);
if (top.get_type() != sajson::TYPE_OBJECT)
fail("");
for (size_t i = 0; i != top.get_length(); ++i) {
std::string category_name = "_" + top.get_object_key(i).as_string() + ".";
sajson::value category = top.get_object_value(i);
if (category.get_type() != sajson::TYPE_OBJECT ||
category.get_length() == 0 ||
category.get_object_value(0).get_type() != sajson::TYPE_ARRAY)
fail("");
size_t cif_cols = category.get_length();
size_t cif_rows = category.get_object_value(0).get_length();
if (cif_rows > 1) {
items.emplace_back(LoopArg{});
Loop& loop = items.back().loop;
loop.tags.reserve(cif_cols);
loop.values.resize(cif_cols * cif_rows);
}
for (size_t j = 0; j != cif_cols; ++j) {
std::string tag = category_name + category.get_object_key(j).as_string();
sajson::value arr = category.get_object_value(j);
if (arr.get_type() != sajson::TYPE_ARRAY)
fail("Expected array, got " + json_type_as_string(arr.get_type()));
if (arr.get_length() != cif_rows)
fail("Expected array of length ", std::to_string(cif_rows), " not ",
std::to_string(arr.get_length()));
if (cif_rows == 1) {
items.emplace_back(tag, as_cif_value(arr.get_array_element(0)));
} else {
Loop& loop = items.back().loop;
loop.tags.emplace_back(std::move(tag));
for (size_t k = 0; k != cif_rows; ++k)
loop.values[j + k*cif_cols] = as_cif_value(arr.get_array_element(k));
}
}
}
}

// reads mmJSON file mutating the input buffer as a side effect
inline Document read_mmjson_insitu(char* buffer, size_t size,
const std::string& name="mmJSON") {
Document doc;
sajson::document json = sajson::parse(sajson::dynamic_allocation(),
sajson::mutable_string_view(size, buffer));
if (!json.is_valid())
fail(name + ":", std::to_string(json.get_error_line()), " error: ",
json.get_error_message_as_string());
fill_document_from_sajson(doc, json);
doc.source = name;
return doc;
}
GEMMI_DLL Document read_mmjson_insitu(char* buffer, std::size_t size,
const std::string& name="mmJSON");

inline Document read_mmjson_file(const std::string& path) {
CharArray buffer = read_file_into_buffer(path);
Expand Down
113 changes: 113 additions & 0 deletions src/json.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright Global Phasing Ltd.

#include <gemmi/json.hpp>
#include <algorithm> // for move

#define SAJSON_UNSORTED_OBJECT_KEYS
#define SAJSON_NUMBERS_AS_STRINGS
#include "../third_party/sajson.h"

namespace gemmi {
namespace cif {
using std::size_t;

static std::string json_type_as_string(sajson::type t) {
switch (t) {
case sajson::TYPE_INTEGER: return "<integer>";
case sajson::TYPE_DOUBLE: return "<double>";
case sajson::TYPE_NULL: return "<null>";
case sajson::TYPE_FALSE: return "<false>";
case sajson::TYPE_TRUE: return "<true>";
case sajson::TYPE_STRING: return "<string>";
case sajson::TYPE_ARRAY: return "<array>";
case sajson::TYPE_OBJECT: return "<object>";
default: return "<unknown type>";
}
}

static std::string as_cif_value(const sajson::value& val) {
switch (val.get_type()) {
case sajson::TYPE_DOUBLE:
return val.as_string();
case sajson::TYPE_NULL:
return "?";
// mmJSON files from PDBj (this format has no spec) have special support
// for boolean YES|NO, which is used only in category _em_specimen.
// IMO it's a bad idea, but we must handle it if we want to read mmJSON.
case sajson::TYPE_FALSE:
return "NO"; // "." in CIF-JSON
case sajson::TYPE_TRUE:
return "YES";
case sajson::TYPE_STRING:
return quote(val.as_string());
default:
fail("Unexpected ", json_type_as_string(val.get_type()), " in JSON.");
return "";
}
}

static void fill_document_from_sajson(Document& d, const sajson::document& s) {
// assuming mmJSON here, we'll add handling of CIF-JSON later on
sajson::value root = s.get_root();
if (root.get_type() != sajson::TYPE_OBJECT || root.get_length() != 1)
fail("not mmJSON");
std::string block_name = root.get_object_key(0).as_string();
if (!starts_with(block_name, "data_"))
fail("not mmJSON - top level key should start with data_\n"
"(if you use gemmi-cif2json to write JSON, use -m for mmJSON)");
d.blocks.emplace_back(block_name.substr(5));
std::vector<Item>& items = d.blocks[0].items;
sajson::value top = root.get_object_value(0);
if (top.get_type() != sajson::TYPE_OBJECT)
fail("");
for (size_t i = 0; i != top.get_length(); ++i) {
std::string category_name = "_" + top.get_object_key(i).as_string() + ".";
sajson::value category = top.get_object_value(i);
if (category.get_type() != sajson::TYPE_OBJECT ||
category.get_length() == 0 ||
category.get_object_value(0).get_type() != sajson::TYPE_ARRAY)
fail("");
size_t cif_cols = category.get_length();
size_t cif_rows = category.get_object_value(0).get_length();
if (cif_rows > 1) {
items.emplace_back(LoopArg{});
Loop& loop = items.back().loop;
loop.tags.reserve(cif_cols);
loop.values.resize(cif_cols * cif_rows);
}
for (size_t j = 0; j != cif_cols; ++j) {
std::string tag = category_name + category.get_object_key(j).as_string();
sajson::value arr = category.get_object_value(j);
if (arr.get_type() != sajson::TYPE_ARRAY)
fail("Expected array, got " + json_type_as_string(arr.get_type()));
if (arr.get_length() != cif_rows)
fail("Expected array of length ", std::to_string(cif_rows), " not ",
std::to_string(arr.get_length()));
if (cif_rows == 1) {
items.emplace_back(tag, as_cif_value(arr.get_array_element(0)));
} else {
Loop& loop = items.back().loop;
loop.tags.emplace_back(std::move(tag));
for (size_t k = 0; k != cif_rows; ++k)
loop.values[j + k*cif_cols] = as_cif_value(arr.get_array_element(k));
}
}
}
}

Document read_mmjson_insitu(char* buffer, size_t size, const std::string& name) {
Document doc;
sajson::document json = sajson::parse(sajson::dynamic_allocation(),
sajson::mutable_string_view(size, buffer));
if (!json.is_valid())
fail(name + ":", std::to_string(json.get_error_line()), " error: ",
json.get_error_message_as_string());
fill_document_from_sajson(doc, json);
doc.source = name;
return doc;
}



} // namespace cif
} // namespace gemmi
File renamed without changes.

0 comments on commit 199c800

Please sign in to comment.