Is there a way to simplify "if (a.prop != null) b.prop = a.prop;"? With a macro perhaps?

Hello,

I have lots of database reading in code where I set the properties of a normal object to the same property of a Dynamic, only if the Dynamic’s property is not null.

Here’s an example of what I mean:

class Dog {
  public var name = 'Default';
  public var age = 0;
  public var owner = 'None';
}

var dynamicDog = {
    name: 'Daisy',
    age: 13
}

var dog: Dog = new Dog();
if (dynamicDog.name != null) dog.name = dynamicDog.name;
if (dynamicDog.age != null) dog.name = dynamicDog.age;
if (dynamicDog.owner != null) dog.owner = dynamicDog.owner;

But suppose I have multiple dozens of properties on my class and I need to do this for every single one of them.

What is a preferred solution? Is there a macro for this? I will gladly take non-macro solutions too!

Thanks a lot!

I’ve written a macro to do this in the Haxe language server to apply default settings recursively. It’s for structures rather than class instances, but it shouldn’t be too much work to adapt it:

Usage:

1 Like

Thanks a lot! Wow, this is pretty cool! Let’s hope it does the job I want. I am fairly new to macros. Any other simpler solution?

Franco Ponticelli’s thx.core library (GitHub - fponticelli/thx.core: Super-charged standard library for Haxe.) has a very handy thx.core.Nulls.or() method that can be used as static extension.

Example from docs (GitHub - fponticelli/thx.core: Super-charged standard library for Haxe.):

using thx.Nulls;
function main() {
	var s:String = null;
	trace(s.or('b')); // prints 'b'
	s = 'a';
	trace(s.or('b')); // prints 'a'
	// or more complex
	var o:{a:{b:{c:String}}} = null;
	trace((o.a.b.c).or("B")); // prints 'B'
	var o = {a: {b: {c: 'A'}}};
	trace((o.a.b.c).or("B")); // prints 'A'
}

Hej,

I have an old custom function that do also something like that : Try Haxe !
I should rewrite it because I’ve written that years ago, I use it often and it works fine but I think something more clean can be done.

using Macro;

class Test {
  static function main() {
    test();
  }
  
  static function test( ?o : Dynamic ){
		trace( o.prop.subProp.ns( "foo" ) ); // "foo"
  }
}

So there’s two approaches, runtime or compile-time

Runtime is how you’d do this in js – you iterate the fields of dynamicDog and check for null before assigning to Dog. In haxe, querying fields is done with the Reflect class:

live link for this example try.haxe.org/#aF39Ea71

// copy not-null fields from dog to dynamicDog
for (field in Reflect.fields(dynamicDog)) {
	var value = Reflect.field(dynamicDog, field);
	if (value != null) {
		Reflect.setField(dog, field, value);
	}
}

This works, but sometimes (especially if performance matters) you may want to not iterate fields at runtime. So you can generate something like the code you’ve written automatically:

live link for this example https://try.haxe.org/#F2d0019A
AssignMacro.hx

import haxe.macro.Context;

macro function assign(newValues, target) {
	// let's find out what fields newValues has

	// get the type of the newValues expression
	var newValuesType = Context.typeof(newValues);
	// follow it in to the root type case it's a typedef or abstract
	newValuesType = Context.followWithAbstracts(newValuesType);

	// it could be many types, but we only care about if it's a structure type (TAnonymous)
	switch newValuesType {
		case TAnonymous(aAnon):
			// now we can get all the fields of this type
			var fields = aAnon.get().fields;

			// now we have the field names, we can generate an expression similar to what you're typing out manually
			var assignmentExpressions = [for (field in fields) {
				var fieldName = field.name;
				// rather than assembling the abstract syntax tree for this expression by hand, we can use the macro keyword to turn haxe expressions into syntax nodes
				macro if (newValues.$fieldName != null) target.$fieldName = newValues.$fieldName;
			}];

			// we have an array of assignment expressions, we can combine those to a single block expression using $b{}
			// more about this here: https://haxe.org/manual/macro-reification-expression.html
			return macro {
				// here we give variables names to the expressions so we can reference them in the assignment expressions
				var newValues = $newValues;
				var target = $target;
				$b{assignmentExpressions};
			}
		default:
			Context.fatalError('Expected structure for this argument', newValues.pos);
			return macro null;
	}
}

If you use this often, you could add import AssignMacro to a file called import.hx, then you’ll be able to use assign() anywhere, more info: Import defaults / import.hx - Haxe - The Cross-platform Toolkit


Given your dynamicDog is almost the same as Dog, just with optional fields, the Partial<T> macro might be useful – it takes a type and gives you that type back as a structure type but with all the fields set to optional

Thanks all for the answers! I like the approach with Reflection. I don’t mind doing it at runtime.

Cheers everyone!

?? operator proposal for reference:

1 Like

I have written a small and a bit hacky macro for safe evaluation here: Try Haxe !
every field that should be checked against null must be postfixed with !. If one of the checks fails, it returns the optional default value - or null if no default is given.
Works with method calls too - here first the method field is checked for existence and after the call the result - so something like this trace(Macro.safe(f!.bar()!.foo("bar")!.b, 42)) evaluates safely.
I am not good in writing macros - I am sure it can be done more elegant :slight_smile: