How to generate class/structure from JSON?
Adam D. Ruppe
destructionator at gmail.com
Mon Sep 9 15:23:58 UTC 2019
On Monday, 9 September 2019 at 11:55:09 UTC, Suliman wrote:
> I have complex JSON. I need to generate class/structure from
> it. Is there any tools for it?
I have a program that kinda does it.
var a = json!q{ "foo": 12, "bar":"hi"}
toStaticType(a);
"struct {
string bar;
long foo;
}"
Here's a program that extracts that part from my other project
into a single command line thing.
It depends on this file as an import:
https://github.com/adamdruppe/arsd/blob/master/jsvar.d
---
import arsd.jsvar;
import std.stdio;
import std.file;
void main(string[] args) {
var a = var.fromJson(readText(args[1]));
auto context = new JsonAnalyzerContext();
auto answer = structsFromJson(a, context).toString();
writeln(answer);
}
class JsonAnalyzerContext {
static class JsonType {
JsonType commonType(JsonType type) {
if(typeid(type) == typeid(this))
return type; // same class == match
if(type == this)
return this; // structural equality also equals match
return JsonVar.singleton;
}
override string toString() {
return toBasicString(0);
}
final string indent(int indentationLevel, string s) {
if(indentationLevel == 0)
return s;
string magic;
foreach(i; 0 .. indentationLevel)
magic ~= "\t";
import std.array;
return magic ~ s;// replace(s, "\n", "\n" ~ magic);
}
string toBasicString(int indentationLevel) {
return indent(indentationLevel, "var");
}
}
static class JsonArray : JsonType {
this(JsonType of) {
assert(of !is null);
arrayOf = of;
}
JsonType arrayOf;
override JsonType commonType(JsonType t) {
if(auto array = cast(JsonArray) t) {
return new JsonArray(arrayOf.commonType(array.arrayOf));
} else if(auto n = cast(JsonNull) t) {
return this;
}
return super.commonType(t);
}
override string toBasicString(int indentationLevel) {
return arrayOf.toBasicString(indentationLevel) ~ "[]";
}
}
static class JsonObject : JsonType {
JsonType[string] members;
JsonObject[string] memberStructs;
bool nullable;
override string toBasicString(int indentationLevel) {
string code = indent(indentationLevel, (nullable ? "@nullable
" : "") ~ "struct {\n");
foreach(k, v; members) {
code ~= v.toBasicString(indentationLevel + 1) ~ " " ~ k ~
";\n";
}
code ~= indent(indentationLevel, "}");
return code;
}
void addMember(string name, JsonType type) {
if(auto ptr = name in members) {
(*ptr) = ptr.commonType(type);
} else
members[name] = type;
}
override JsonType commonType(JsonType type) {
if(cast(JsonNull) type) {
this.nullable = true;
return this;
}
return super.commonType(type);
}
}
static class JsonInteger : JsonType {
__gshared static typeof(this) singleton = new JsonInteger();
override JsonType commonType(JsonType type) {
if(auto dbl = cast(JsonFloat) type)
return dbl; // double is the common type
return super.commonType(type);
}
override string toBasicString(int indentationLevel) {
return indent(indentationLevel, "long");
}
}
static class JsonFloat : JsonType {
__gshared static typeof(this) singleton = new JsonFloat();
override JsonType commonType(JsonType type) {
if(auto i = cast(JsonInteger) type)
return this; // double is the most common type of long and
double
return super.commonType(type);
}
override string toBasicString(int indentationLevel) {
return indent(indentationLevel, "double");
}
}
static class JsonBoolean : JsonType {
__gshared static typeof(this) singleton = new JsonBoolean();
// true/false could implicitly convert to string or int, but
// it really shouldn't. If that happens in the json, we'll just
fall
// back on null.
override string toBasicString(int indentationLevel) {
return indent(indentationLevel, "bool");
}
}
static class JsonNull : JsonType {
__gshared static typeof(this) singleton = new JsonNull();
override JsonType commonType(JsonType type) {
// strings and arrays can be null in D too, so we
// return them if possible
if(auto str = cast(JsonString) type)
return str;
if(auto arr = cast(JsonArray) type)
return arr;
// however, since json objects are represented as structs,
// they cannot be null!
if(auto obj = cast(JsonObject) type) {
obj.nullable = true;
return obj;
}
return super.commonType(type);
}
override string toBasicString(int indentationLevel) {
return indent(indentationLevel, "typeof(null)");
}
}
static class JsonString : JsonType {
__gshared static typeof(this) singleton = new JsonString();
override JsonType commonType(JsonType t) {
if(auto n = cast(JsonNull) t) {
return this;
}
return super.commonType(t);
}
override string toBasicString(int indentationLevel) {
return indent(indentationLevel, "string");
}
}
static class JsonFunction : JsonType {
__gshared static typeof(this) singleton = new JsonFunction();
// this should never happen!
override string toBasicString(int indentationLevel) {
return indent(indentationLevel, "typeof(null) /* function */");
}
}
static class JsonVar : JsonType {
__gshared static typeof(this) singleton = new JsonVar();
// this is the supertype of everything
override string toBasicString(int indentationLevel) {
return indent(indentationLevel, "var");
}
}
JsonObject findObject(string name) {
if(auto it = name in objectTypes)
return *it;
auto i = new JsonAnalyzerContext.JsonObject();
objectTypes[name] = i;
return i;
}
JsonObject[string] objectTypes;
}
/// This analyzes a JSON input and tries to return matching
static D types,
/// as a code string. It assumes that all members of an array
have a common
/// type and all elements have the same members as the first one.
JsonAnalyzerContext.JsonType structsFromJson(var input,
JsonAnalyzerContext context, JsonAnalyzerContext.JsonType
suggestedType = null, string contextName = null) {
final switch(input.payloadType()) {
case var.Type.Object:
if(input == null)
return JsonAnalyzerContext.JsonNull.singleton;
auto objSuggestion = cast(JsonAnalyzerContext.JsonObject)
suggestedType;
auto type = objSuggestion ? objSuggestion :
context.findObject(contextName);
foreach(k, v; input) {
auto name = k.get!string;
type.addMember(name, structsFromJson(v, context, null,
contextName ~ "." ~ name));
}
return type;
case var.Type.Array:
JsonAnalyzerContext.JsonType common = null;
foreach(v; input) {
auto t = structsFromJson(v, context, common, contextName);
if(common is null)
common = t;
else
common = common.commonType(t);
}
if(common is null)
common = JsonAnalyzerContext.JsonVar.singleton;
return new JsonAnalyzerContext.JsonArray(common);
case var.Type.String:
return JsonAnalyzerContext.JsonString.singleton;
case var.Type.Integral:
return JsonAnalyzerContext.JsonInteger.singleton;
case var.Type.Floating:
return JsonAnalyzerContext.JsonFloat.singleton;
case var.Type.Boolean:
return JsonAnalyzerContext.JsonBoolean.singleton;
case var.Type.Function:
return JsonAnalyzerContext.JsonFunction.singleton;
}
}
----
Note that you will have to edit the generated code a little. It
doesn't generate 100% valid D out of the box, the names might be
keywords and the structs it creates do not have names. For
example:
struct {
bool bool2;
struct {
long id;
string name;
}[] objarr;
double float;
typeof(null) null;
string test;
long int;
struct {
long[] longarray;
string str;
long subnumber;
} object;
bool bool1;
long[] array;
string rls;
}
That's one output and obviously that's not 100% valid D - the
structs need names and child object syntax isn't quite right.
But it helps get you started reading a big json file into it.
More information about the Digitalmars-d
mailing list