diff --git a/src/json2object/utils/TypeTools.hx b/src/json2object/utils/TypeTools.hx index 0bc6760..6a2fee2 100644 --- a/src/json2object/utils/TypeTools.hx +++ b/src/json2object/utils/TypeTools.hx @@ -38,6 +38,24 @@ class TypeTools { #if macro + public static function isMap(type:Type):Bool { + var t = follow(type); + switch (t) { + case TAbstract(_.get() => a, [keyType, valueType]): + if (a.pack.join(".") == "haxe.ds" && a.name == "Map") return true; + else return false; + + case TInst(_.get() => c, [keyType, valueType]): + for (i in c.interfaces) + if (i.t.get().pack.join(".") == "haxe" && i.t.get().name == "IMap") return true; + return false; + + default: + return false; + } + } + + public static inline function toComplexType(type:Null):Null { return { inline function direct() diff --git a/src/json2object/writer/DataBuilder.hx b/src/json2object/writer/DataBuilder.hx index 5ba17b4..5d93a81 100644 --- a/src/json2object/writer/DataBuilder.hx +++ b/src/json2object/writer/DataBuilder.hx @@ -126,13 +126,32 @@ class DataBuilder { } return macro { + order = order ?? []; + var indent = buildIndent(space, level); var firstIndent = (indentFirst) ? indent : ''; if (o == null) { return firstIndent + "null"; } var valueWriter = new $clsValue(ignoreNullOptionals); @:privateAccess { - var values = [for (key in o.keys()) indent + space + '"'+key+'": '+valueWriter._write(o.get(key), space, level + 1, false, onAllOptionalNull)]; + var values = [ + for (key in order) + if (o.exists(key)) + indent + space + '"'+key+'": '+valueWriter._write(o.get(key), space, level + 1, false, onAllOptionalNull) + ]; + + var remainingKeys = [ + for (key in o.keys()) + if (!order.contains(key)) + key + ]; + remainingKeys.sort(Reflect.compare); + + values = values.concat([ + for (key in remainingKeys) + indent + space + '"'+key+'": '+valueWriter._write(o.get(key), space, level + 1, false, onAllOptionalNull) + ]); + var newLine = (space != '' && values.length > 0) ? '\n' : ''; var json = firstIndent+'{' + newLine; @@ -176,7 +195,8 @@ class DataBuilder { } var assignations:Array = []; - var skips: Array = []; + var nullSkips: Array = []; + var defaultSkips: Array = []; for (field in fields) { if (field.meta.has(":jignored")) { continue; } @@ -218,6 +238,9 @@ class DataBuilder { } else if (field.meta.has(":noquoting")) { assignation = macro $assignation + new $f_cls(ignoreNullOptionals).dontQuote()._write(cast $f_a, space, level + 1, false, onAllOptionalNull); + } else if (field.meta.has(":order")) { + var order = field.meta.extract(":order")[0].params[0]; + assignation = macro $assignation + new $f_cls(ignoreNullOptionals)._write(cast $f_a, space, level + 1, false, onAllOptionalNull, $order); } else { assignation = macro $assignation + new $f_cls(ignoreNullOptionals)._write(cast $f_a, space, level + 1, false, onAllOptionalNull); } @@ -228,23 +251,72 @@ class DataBuilder { case TAbstract(t, params): if (t.toString() == "Null") { // Null - skips.push(macro $f_a == null); + nullSkips.push(macro $f_a == null); } else { // Bool - skips.push(macro false); + nullSkips.push(macro false); + } + default: + nullSkips.push(macro $f_a == null); + } + } else { + nullSkips.push(macro false); + } + + if (field.meta.has(":default")) { + // Get the value of the @:default annotation. + var f_default:Expr = macro ${field.meta.extract(":default")[0].params[0]}; + + switch (f_default.expr) { + case ECall(e, params): + // You cannot directly compare enums with arguments. + // Here, we don't skip the value, and assume @:jcustomparse will handle converting to a primative. + defaultSkips.push(macro false); + case EArrayDecl(values): + if (field.type.isMap()) { + var f_shouldskip:Expr = values.length == 0 ? macro !$f_a.keys().hasNext() : macro { + var defaultMap = $f_default; // store in local variable to avoid multiple evaluation + var skip:Bool = true; + for (k in $f_a.keys()) { + if (!defaultMap.exists(k) || $f_a.get(k) != defaultMap.get(k)) { + skip = false; + break; + } + } + skip; + } + defaultSkips.push(f_shouldskip); + } else { + var f_shouldskip:Expr = macro $f_a.length == $f_default.length ? { + var defaultArray = $f_default; // store in local variable to avoid multiple evaluation + var skip:Bool = true; + for (i in 0...$f_a.length) { + if ($f_a[i] != defaultArray[i]) { + skip = false; + break; + } + } + skip; + } : false; + defaultSkips.push(f_shouldskip); } default: - skips.push(macro $f_a == null); + // Compare the default value with the value of the field. + // If the values are the same, skip the value. + var f_shouldskip:Expr = macro $f_a == $f_default; + defaultSkips.push(f_shouldskip); } } else { - skips.push(macro false); + // Never skip if there is no @:default annotation. + defaultSkips.push(macro false); } default: } } var array = {expr:EArrayDecl(assignations), pos:Context.currentPos()}; - var skips = {expr:EArrayDecl(skips), pos:Context.currentPos()}; + var nullSkips = {expr:EArrayDecl(nullSkips), pos:Context.currentPos()}; + var defaultSkips = {expr:EArrayDecl(defaultSkips), pos:Context.currentPos()}; return macro { var indent = buildIndent(space, level); @@ -252,14 +324,17 @@ class DataBuilder { if (o == null) { return firstIndent + "null"; } @:privateAccess{ var decl = ${array}; + var defaultSkips = ${defaultSkips}; if (ignoreNullOptionals) { - var skips = ${skips}; - if (skips.indexOf(false) == -1) { + var nullSkips = ${nullSkips}; + if (nullSkips.indexOf(false) == -1) { decl = onAllOptionalNull != null ? [onAllOptionalNull()] : []; } else { - decl = [ for (i in 0...decl.length) skips[i] ? continue : decl[i]]; + decl = [ for (i in 0...decl.length) (nullSkips[i] || defaultSkips[i]) ? continue : decl[i]]; } + } else { + decl = [ for (i in 0...decl.length) (defaultSkips[i]) ? continue : decl[i]]; } var newLine = (space != '' && decl.length > 0) ? '\n' : ''; @@ -428,6 +503,9 @@ class DataBuilder { } }; + var isMapWriter:Bool = false; + var mapKey:Null = null; + var writeExpr = switch (type) { case TInst(_.get()=>t, p) : switch(t.module) { @@ -474,6 +552,8 @@ class DataBuilder { } } else if (t.module == #if (haxe_ver >= 4) "haxe.ds.Map" #else "Map" #end) { + isMapWriter = true; + mapKey = p[0].toComplexType(); makeMapWriter(p[0], p[1], c); } else { @@ -507,6 +587,8 @@ class DataBuilder { {name:"indentFirst", meta:null, opt:false, type:Context.getType("Bool").toComplexType(), value:macro false}, {name:"onAllOptionalNull", meta:null, opt:true, type:onAllOptionalNullCT, value: macro null} ]; + if(isMapWriter) + args.push({name:"order", meta:null, opt:true, type:(macro: Array<$mapKey>), value:macro null}); var privateWrite:Field = { doc: null, kind: FFun({args:args, expr:writeExpr, params:null, ret:null}),