forked from ddnet/ddnet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port the `CJsonWriter` utility class from upstream, which makes outputting correct JSON easier. Add `CJsonWriter` as an abstract class that can write to different outputs. Two implementations `CJsonFileWriter` (writes to a file) and `CJsonStringWriter` (writes to an `std::string`) are added. Upstream `CJsonWriter` can only write to files. The same tests are added for both implementations. Duplicate code is avoided by using typed tests with two separate test fixtures.
- Loading branch information
Showing
4 changed files
with
552 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ | ||
/* If you are missing that file, acquire a complete release at teeworlds.com. */ | ||
|
||
#include "jsonwriter.h" | ||
|
||
static char EscapeJsonChar(char c) | ||
{ | ||
switch(c) | ||
{ | ||
case '\"': return '\"'; | ||
case '\\': return '\\'; | ||
case '\b': return 'b'; | ||
case '\n': return 'n'; | ||
case '\r': return 'r'; | ||
case '\t': return 't'; | ||
// Don't escape '\f', who uses that. :) | ||
default: return 0; | ||
} | ||
} | ||
|
||
CJsonWriter::CJsonWriter() | ||
{ | ||
m_Indentation = 0; | ||
} | ||
|
||
void CJsonWriter::BeginObject() | ||
{ | ||
dbg_assert(CanWriteDatatype(), "Cannot write object at this position"); | ||
WriteIndent(false); | ||
WriteInternal("{"); | ||
PushState(STATE_OBJECT); | ||
} | ||
|
||
void CJsonWriter::EndObject() | ||
{ | ||
dbg_assert(TopState()->m_Kind == STATE_OBJECT, "Cannot end object here"); | ||
PopState(); | ||
CompleteDataType(); | ||
WriteIndent(true); | ||
WriteInternal("}"); | ||
} | ||
|
||
void CJsonWriter::BeginArray() | ||
{ | ||
dbg_assert(CanWriteDatatype(), "Cannot write array at this position"); | ||
WriteIndent(false); | ||
WriteInternal("["); | ||
PushState(STATE_ARRAY); | ||
} | ||
|
||
void CJsonWriter::EndArray() | ||
{ | ||
dbg_assert(TopState()->m_Kind == STATE_ARRAY, "Cannot end array here"); | ||
PopState(); | ||
CompleteDataType(); | ||
WriteIndent(true); | ||
WriteInternal("]"); | ||
} | ||
|
||
void CJsonWriter::WriteAttribute(const char *pName) | ||
{ | ||
dbg_assert(TopState()->m_Kind == STATE_OBJECT, "Attribute can only be written inside of objects"); | ||
WriteIndent(false); | ||
WriteInternalEscaped(pName); | ||
WriteInternal(": "); | ||
PushState(STATE_ATTRIBUTE); | ||
} | ||
|
||
void CJsonWriter::WriteStrValue(const char *pValue) | ||
{ | ||
dbg_assert(CanWriteDatatype(), "Cannot write value at this position"); | ||
WriteIndent(false); | ||
WriteInternalEscaped(pValue); | ||
CompleteDataType(); | ||
} | ||
|
||
void CJsonWriter::WriteIntValue(int Value) | ||
{ | ||
dbg_assert(CanWriteDatatype(), "Cannot write value at this position"); | ||
WriteIndent(false); | ||
char aBuf[32]; | ||
str_format(aBuf, sizeof(aBuf), "%d", Value); | ||
WriteInternal(aBuf); | ||
CompleteDataType(); | ||
} | ||
|
||
void CJsonWriter::WriteBoolValue(bool Value) | ||
{ | ||
dbg_assert(CanWriteDatatype(), "Cannot write value at this position"); | ||
WriteIndent(false); | ||
WriteInternal(Value ? "true" : "false"); | ||
CompleteDataType(); | ||
} | ||
|
||
void CJsonWriter::WriteNullValue() | ||
{ | ||
dbg_assert(CanWriteDatatype(), "Cannot write value at this position"); | ||
WriteIndent(false); | ||
WriteInternal("null"); | ||
CompleteDataType(); | ||
} | ||
|
||
bool CJsonWriter::CanWriteDatatype() | ||
{ | ||
return m_States.empty() || TopState()->m_Kind == STATE_ARRAY || TopState()->m_Kind == STATE_ATTRIBUTE; | ||
} | ||
|
||
void CJsonWriter::WriteInternalEscaped(const char *pStr) | ||
{ | ||
WriteInternal("\""); | ||
int UnwrittenFrom = 0; | ||
int Length = str_length(pStr); | ||
for(int i = 0; i < Length; i++) | ||
{ | ||
char SimpleEscape = EscapeJsonChar(pStr[i]); | ||
// Assuming ASCII/UTF-8, exactly everything below 0x20 is a | ||
// control character. | ||
bool NeedsEscape = SimpleEscape || (unsigned char)pStr[i] < 0x20; | ||
if(NeedsEscape) | ||
{ | ||
if(i - UnwrittenFrom > 0) | ||
{ | ||
WriteInternal(pStr + UnwrittenFrom, i - UnwrittenFrom); | ||
} | ||
|
||
if(SimpleEscape) | ||
{ | ||
char aStr[2]; | ||
aStr[0] = '\\'; | ||
aStr[1] = SimpleEscape; | ||
WriteInternal(aStr, sizeof(aStr)); | ||
} | ||
else | ||
{ | ||
char aStr[7]; | ||
str_format(aStr, sizeof(aStr), "\\u%04x", pStr[i]); | ||
WriteInternal(aStr); | ||
} | ||
UnwrittenFrom = i + 1; | ||
} | ||
} | ||
if(Length - UnwrittenFrom > 0) | ||
{ | ||
WriteInternal(pStr + UnwrittenFrom, Length - UnwrittenFrom); | ||
} | ||
WriteInternal("\""); | ||
} | ||
|
||
void CJsonWriter::WriteIndent(bool EndElement) | ||
{ | ||
const bool NotRootOrAttribute = !m_States.empty() && TopState()->m_Kind != STATE_ATTRIBUTE; | ||
|
||
if(NotRootOrAttribute && !TopState()->m_Empty && !EndElement) | ||
WriteInternal(","); | ||
|
||
if(NotRootOrAttribute || EndElement) | ||
WriteInternal(IO_NEWLINE); | ||
|
||
if(NotRootOrAttribute) | ||
for(int i = 0; i < m_Indentation; i++) | ||
WriteInternal("\t"); | ||
} | ||
|
||
void CJsonWriter::PushState(EJsonStateKind NewState) | ||
{ | ||
if(!m_States.empty()) | ||
{ | ||
m_States.top().m_Empty = false; | ||
} | ||
m_States.push(SState(NewState)); | ||
if(NewState != STATE_ATTRIBUTE) | ||
{ | ||
m_Indentation++; | ||
} | ||
} | ||
|
||
CJsonWriter::SState *CJsonWriter::TopState() | ||
{ | ||
dbg_assert(!m_States.empty(), "json stack is empty"); | ||
return &m_States.top(); | ||
} | ||
|
||
CJsonWriter::EJsonStateKind CJsonWriter::PopState() | ||
{ | ||
dbg_assert(!m_States.empty(), "json stack is empty"); | ||
SState TopState = m_States.top(); | ||
m_States.pop(); | ||
if(TopState.m_Kind != STATE_ATTRIBUTE) | ||
{ | ||
m_Indentation--; | ||
} | ||
return TopState.m_Kind; | ||
} | ||
|
||
void CJsonWriter::CompleteDataType() | ||
{ | ||
if(!m_States.empty() && TopState()->m_Kind == STATE_ATTRIBUTE) | ||
PopState(); // automatically complete the attribute | ||
|
||
if(!m_States.empty()) | ||
TopState()->m_Empty = false; | ||
} | ||
|
||
CJsonFileWriter::CJsonFileWriter(IOHANDLE IO) | ||
{ | ||
dbg_assert((bool)IO, "IO handle invalid"); | ||
m_IO = IO; | ||
} | ||
|
||
CJsonFileWriter::~CJsonFileWriter() | ||
{ | ||
// Ensure newline at the end | ||
WriteInternal(IO_NEWLINE); | ||
io_close(m_IO); | ||
} | ||
|
||
void CJsonFileWriter::WriteInternal(const char *pStr, int Length) | ||
{ | ||
io_write(m_IO, pStr, Length < 0 ? str_length(pStr) : Length); | ||
} | ||
|
||
void CJsonStringWriter::WriteInternal(const char *pStr, int Length) | ||
{ | ||
m_OutputString += Length < 0 ? pStr : std::string(pStr, Length); | ||
} | ||
|
||
std::string CJsonStringWriter::GetOutputString() const | ||
{ | ||
// Ensure newline at the end | ||
return m_OutputString + IO_NEWLINE; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ | ||
/* If you are missing that file, acquire a complete release at teeworlds.com. */ | ||
#ifndef ENGINE_SHARED_JSONWRITER_H | ||
#define ENGINE_SHARED_JSONWRITER_H | ||
|
||
#include <base/system.h> | ||
|
||
#include <stack> | ||
|
||
class CJsonWriter | ||
{ | ||
enum EJsonStateKind | ||
{ | ||
STATE_OBJECT, | ||
STATE_ARRAY, | ||
STATE_ATTRIBUTE, | ||
}; | ||
|
||
struct SState | ||
{ | ||
EJsonStateKind m_Kind; | ||
bool m_Empty = true; | ||
|
||
SState(EJsonStateKind Kind) : | ||
m_Kind(Kind) | ||
{ | ||
} | ||
}; | ||
|
||
std::stack<SState> m_States; | ||
int m_Indentation; | ||
|
||
bool CanWriteDatatype(); | ||
void WriteInternalEscaped(const char *pStr); | ||
void WriteIndent(bool EndElement); | ||
void PushState(EJsonStateKind NewState); | ||
SState *TopState(); | ||
EJsonStateKind PopState(); | ||
void CompleteDataType(); | ||
|
||
protected: | ||
// String must be zero-terminated when Length is -1 | ||
virtual void WriteInternal(const char *pStr, int Length = -1) = 0; | ||
|
||
public: | ||
CJsonWriter(); | ||
virtual ~CJsonWriter() = default; | ||
|
||
// The root is created by beginning the first datatype (object, array, value). | ||
// The writer must not be used after ending the root, which must be unique. | ||
|
||
// Begin writing a new object | ||
void BeginObject(); | ||
// End current object | ||
void EndObject(); | ||
|
||
// Begin writing a new array | ||
void BeginArray(); | ||
// End current array | ||
void EndArray(); | ||
|
||
// Write attribute with the given name inside the current object. | ||
// Names inside one object should be unique, but this is not checked here. | ||
// Must be used to begin writing anything inside objects and only there. | ||
// Must be followed by a datatype for the attribute value. | ||
void WriteAttribute(const char *pName); | ||
|
||
// Methods for writing value literals | ||
// - As array values in arrays | ||
// - As attribute values after beginning an attribute inside an object | ||
// - As root value (only once) | ||
void WriteStrValue(const char *pValue); | ||
void WriteIntValue(int Value); | ||
void WriteBoolValue(bool Value); | ||
void WriteNullValue(); | ||
}; | ||
|
||
// Writes JSON to a file | ||
class CJsonFileWriter : public CJsonWriter | ||
{ | ||
IOHANDLE m_IO; | ||
|
||
protected: | ||
void WriteInternal(const char *pStr, int Length = -1) override; | ||
|
||
public: | ||
// Create a new writer object without writing anything to the file yet. | ||
// The file will automatically be closed by the destructor. | ||
CJsonFileWriter(IOHANDLE IO); | ||
~CJsonFileWriter(); | ||
}; | ||
|
||
// Writes JSON to an std::string | ||
class CJsonStringWriter : public CJsonWriter | ||
{ | ||
std::string m_OutputString; | ||
|
||
protected: | ||
void WriteInternal(const char *pStr, int Length = -1) override; | ||
|
||
public: | ||
CJsonStringWriter() = default; | ||
~CJsonStringWriter() = default; | ||
std::string GetOutputString() const; | ||
}; | ||
|
||
#endif |
Oops, something went wrong.